Нет неоднозначной ошибки ссылки даже после использования директивы namespace

следующий код генерирует call of overloaded ‘bar()’ is ambiguous ошибка, которая должна быть, поскольку у меня есть функция bar как в глобальном, так и foo пространство имен, и я назвал

5 ответов


в разделе C++ standard 17.6.1 содержание и организация библиотеки мы читаем в 17.6.1.2:

за исключением случаев, указанных в пунктах 18-30 И приложении D, содержание каждый заголовок cname должен быть таким же, как у соответствующего имя заголовка.h, как указано в стандартной библиотеке C (1.2) или C Unicode TR, по мере необходимости, как бы путем включения. В C ++ стандартная библиотека, однако, Объявления (за исключением имена, определенные как макросы в C), находятся в области пространства имен (3.3.6) пространства имен std. не указано, являются ли эти имена первыми объявляются в области глобального пространства имен и затем вводятся в пространство имен std с помощью явных объявлений using-(7.3.3).

курсив

кроме того, в 17.6.4.3.2 внешняя связь читаем

каждый имя из стандартной библиотеки C, объявленной с внешней связью зарезервирован для реализации для использования в качестве имени с extern " C" связь, как в пространстве имен std, так и в глобальном пространстве имен

на простом английском языке из этого раздела и подобных, имена стандартных библиотек C зарезервированы, но имена стандартных библиотек C находятся только в области глобального пространства имен.

что GLIBCXX делает здесь вполне допустимо; это объявлении abs в глобальном пространстве имен объем и впрыскивать его в std использование объявлений using -.

действительно, в стандартной библиотеке, которую используют мои system / g++ 4.8.5 и 6.3.0 (6.3.0 я проверил на coliru), <cstdlib> выглядит так:

// <stdlib.h>:

extern int abs (int __x) __THROW __attribute__ ((__const__)) __wur;
// <cstdlib>

#include <stdlib.h>

namespace std
{
    using ::abs;
}

это using ::abs что делает std::abs вызов функции.

вы нарушаете ODR, потому что GLIBC является общей библиотекой, а также предоставляет реализацию для int abs(int).

что вы не получите "множественное определение abs(int)" ошибка компоновщика, возможно, является ошибкой в компиляторах; было бы неплохо, если бы они предупредили об этом неопределенном поведении.


это можно воспроизвести в следующем примере:

main.cpp

#include <iostream>

int myabs(int);

namespace foo {
    int myabs(int n) {
        return ::myabs(n);
    }
}

int myabs(int n) {
    std::cout << "myabs inside main.cpp\n";
    return n > 0 ? n : -n;
}

using namespace foo;

int main() {
    int k = -1;

    std::cout << foo::myabs(k) << std::endl;
}

myabs.cpp

#include <iostream>

int myabs(int n) {
    std::cout << "myabs inside myabs.cpp\n";
    return n > 0 ? n : -n;
}

затем в командной строке:

g++ -fPIC -c myabs.cpp
g++ -shared myabs.o -o libmyabs.so
g++ -L. main.cpp -lmyabs

под управлением ./a.out называет myabs определена внутри main.cpp, а если вы комментируете myabs на main.cpp, он вызывает один из myabs.cpp


Как избежать этой проблемы

если вы избегаете объявления функций в глобальном пространстве имен, вы должны в основном избегать этой проблемы.

для вашего примера, если мы вместо этого напишем:

#include <cstdlib>
#include <iostream>

namespace {
    int abs(int n) {
        return n > 0 ? n : -n;
    }
}

using namespace std;

int main() {
    int k;

    cin >> k;

    cout << abs(k) << endl;
}

мы получаем ожидаемое предупреждение об ошибке о неоднозначности вызова. Однако имейте в виду, что это не решает проблему, если стандартная библиотека объявляет abs в глобальном пространстве имен:

int main() {
    int k;

    cin >> k;

    cout << ::abs(k) << endl;
}

это, кажется, просто вызов стандартной версии библиотеки. Естественно, этой проблемы можно избежать, избегая using namespace std


проблема в том, что <cstdlib> - это действительно сложно из-за взаимодействия между заголовками C и заголовками c++. В libstdc++ он не реализован как:

namespace std {
    int abs(int );
}

если это так, то ваш образец программы с std::abs соответствовало бы вашему ожиданию о вашей программе образца с foo::bar, по тем же причинам. Но вместо этого он объявляется как что-то вроде:

// from <stdlib.h>
extern int abs(int );

// from <cstdlib>
#include <stdlib.h>

namespace std {
    using ::abs;
}

когда вы объявили и определили собственный ::abs(int ), это просто повторное объявление ранее объявленного int ::abs(int ). Вы ничего не перегружаете - есть только один int ::abs(int) в этом блоке перевода! Вы могли бы увидеть это, если бы попытались объявить что-то вроде long abs(int ) - вы получите сообщение об ошибке redeclaration с другим типом возврата.

это работает, потому что ::abs в заголовке C не определен (в противном случае вы получите ошибку компиляции при переопределении) - вы вводите это определение через общую библиотеку. И таким образом, вы заканчиваете с нарушением ODR, потому что у вас есть определение в TU и определение общей библиотеки в GLIBC, и, следовательно, неопределенное поведение. Я не знаю, почему линкер не поймал его.


если abs функция объявляется следующим образом:

void abs(int n) { return n > 0 ? n : -n; } (тип возврата изменен с int to void)

это позволит поднять error: ambiguating new declaration of 'void abs(int)'

потому что stdlib он заявил как int abs(int n) но мы определяем его сейчас с другим типом возврата.

так почему же он не жалуется, когда я определяю его с правильным типом возврата?

прежде всего, реализация int abs(int k) находится в скомпилированном форма (стандартная библиотека) не в исходной форме. Поэтому невозможно сказать (перед связыванием), есть ли int abs(int k) уже определено или нет. Поэтому компилятор доволен объявлением в cstdlib и определение в нашем предоставил источник. И когда он начинает связывать его только поиск функции, которая объявлена, но еще не определена (так что она может скопировать определение (предполагаемая связь со статической библиотекой)). Поэтому linker не будет искать другое определение int abs(int k). Наконец, наше данное определение включено в результирующий двоичный файл.


я заметил следующее внутри <cstdlib>:

#ifndef __CORRECT_ISO_CPP_STDLIB_H_PROTO
  inline long
  abs(long __i) { return __builtin_labs(__i); }
  //...

когда я попробую ваш пример, используя long,

#include <cstdlib>
#include <iostream>

long abs(long n) {
    return n > 0 ? n : -n;
}

using namespace std;

int main() {
    long k;

    cin >> k;

    cout << abs(k) << endl;
}

я получаю ожидаемую ошибку:

error: call of overloaded 'abs(long int&)' is ambiguous

может быть, ваша реализация делает что-то подобное.


Давайте изменим этот код следующим образом:

#include <iostream>
#include <cstdlib>

int abs(int n) {
    std::cout << "default abs\n";
    return n > 0 ? n : -n;
}

//using namespace std;

int main() {
    int k;

    std::cin >> k;

    std::cout << std::abs(k) << std::endl;
}

он по-прежнему будет называть ваш abs. Странно , да? Хорошо, на самом деле нет