Нет неоднозначная ссылка ошибки даже после того, как с помощью директивы пространства имен

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

namespace foo {
    void bar() {}
}

void bar() {}

using namespace foo;

int main() {
    bar();
}

Я тоже ожидал ту же ошибку со следующим кодом:

#include <cstdlib>
#include <iostream>

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

using namespace std;

int main() {
    int k;

    cin >> k;

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

Я определил функцию int abs(int n) как ту, что присутствует в cstlib, и я назвал директиву using namespace std. Таким образом, должна быть ошибка, подобная первому примеру. Но их нет.

Мой вопрос в том, как компилятор разрешает эту двусмысленность? Какую функцию вызывать в таких случаях, мой или std один? Есть ли здесь UB?

Обновление. Из комментариев и ответов кажется, что разные компиляторы ведут себя по-другому. Так это поведение undefined или реализация определена?

Я тестировал его с g++ 4.8.4 на Ubuntu 14.04 с флагом -std=c++11.

[Обратите внимание, что я понимаю, что using namespace std плохой, а моя функция abs не лучше или даже хуже, чем std one. Мой вопрос другой.]

Ответ 1

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

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

выделено фокус

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

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

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

Что GLIBCXX делает здесь, совершенно верно; он объявляет abs в области глобального пространства имен и вводит его в std с помощью использования-деклараций.

Действительно, в стандартной библиотеке, используемой моей системой /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)" ошибки компоновщика, возможно, является ошибкой в ​​компиляторах; было бы неплохо, если бы они предупредили об этом undefined.


Это можно воспроизвести с помощью этого примера:

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

Ответ 2

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

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 ) - вы получите ошибку о повторной декларации с другим типом возврата.

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

Ответ 3

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

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

это поднимет error: ambiguating new declaration of 'void abs(int)'

Потому что в stdlib он объявлен как int abs(int n), но мы определяем его теперь с помощью другого типа возврата.

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

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

Ответ 4

Я заметил следующее внутри <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

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

Ответ 5

Изменить этот код следующим образом:

#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;
}

Он ВСЕГДА будет называть ваш абс. Странно, да? Хорошо, на самом деле нет функции int abs(int) в пространстве имен std. Здесь нет двусмысленного вызова, в зависимости от используемой платформы, поскольку фактический абс определяется как равный этому:

std::intmax_t abs( std::intmax_t n );

Но фактическая реализация может варьироваться в зависимости от ряда факторов. Что вы сделали, так это то, что вы либо перегрузили функцию, либо шаблон. Пока вы не ударяете точное определение в заголовочном файле, ваша функция будет использоваться, если она лучше соответствует аргументам. Он может быть опробован кандидатом по шаблонам std вместо функции std:: abs(), если пространство имен std используется во всем мире. Это одна из предостережений за использование пространства имен std в глобальной области.

на самом деле, в моей системе std:: abs определяется как абс из глобальной области:  Конечно, у вас есть функция из глобальной области действия с таким прототипом, определенным самим собой, поэтому вызов std:: abs в моем случае равен:: abs call.

#include <iostream>
#include <cstdlib>

int abs( long 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;
}

Теперь он использует стандартную библиотечную функцию и выводит абсолютное значение k.

Посмотрим, что содержит заголовок cstdlib в конкретном случае:

_STD_BEGIN
using _CSTD size_t; using _CSTD div_t; using _CSTD ldiv_t;   
using _CSTD abort; using _CSTD abs; using _CSTD atexit;
// and so on..
_STD_END

_STD_BEGIN определяется как

#define _STD_BEGIN  namespace std {

Эффективно мы имеем

namespace std {
     using ::abs;
}

Таким образом, все, что получило идентификатор abs в глобальной области, становится std:: abs. Это вызвало форвардную декларацию, поэтому abs(), определенная после этого определения, является предметом. Поскольку синтаксис языка позволяет, переопределение идентификаторов библиотеки в глобальной области может привести к плохо сформированной программе или к UB, которая в этом случае сводится к тому, какие объявления активны в заголовке.

Стандартная библиотека С++ резервирует следующие типы имен:

  • макросы
  • глобальные имена
  • имена с внешней связью

Если программа объявляет или определяет имя в контексте, где оно зарезервировано, кроме как явно разрешено настоящим пунктом, его поведение undefined.