Нет неоднозначная ссылка ошибки даже после того, как с помощью директивы пространства имен
Следующий код генерирует ошибку 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.