#include <iostream>
#include <cmath>
/* Intentionally incorrect abs() which seems to override std::abs() */
int abs(int a) {
return a > 0? -a : a;
}
int main() {
int a = abs(-5);
int b = std::abs(-5);
std::cout<< a << std::endl << b << std::endl;
return 0;
}
Я ожидал, что выходной будет -5
а также 5
, но на выходе -5
а также -5
,
Интересно, почему это случится?
Имеет ли это какое-либо отношение к использованию std
или что?
Спецификация языка позволяет реализации для реализации <cmath>
путем объявления (и определения) стандартных функций в Глобальный пространство имен, а затем перенести их в пространство имен std
с помощью объявлений об использовании. Не определено, используется ли этот подход
20.5.1.2 Заголовки
4 […] В стандартной библиотеке C ++, однако, объявления (за исключением имен, которые определены как макросы в C) находятся в пределах области имен (6.3.6) пространства именstd
, Не указано, являются ли эти имена (включая любые перегрузки
добавленные в пунктах 21–33 и Приложении D) сначала объявляются в глобальной области имен, а затем внедряются в пространство именstd
с помощью явных объявлений об использовании (10.3.3).
По-видимому, вы имеете дело с одной из реализаций, которая решила следовать этому подходу (например, GCC). То есть ваша реализация обеспечивает ::abs
, в то время как std::abs
просто «относится» к ::abs
,
В этом случае остается один вопрос: почему в дополнение к стандарту? ::abs
Вы смогли заявить о себе ::abs
то есть, почему нет ошибки множественного определения. Это может быть вызвано другой функцией, предоставляемой некоторыми реализациями (например, GCC): они объявляют стандартные функции как так называемые слабые символы, что позволяет вам «заменить» их собственными определениями.
Эти два фактора вместе создают эффект, который вы наблюдаете: замена слабых символов ::abs
также приводит к замене std::abs
, Насколько хорошо это согласуется с языковым стандартом, это отдельная история … В любом случае, не полагайтесь на это поведение — оно не гарантируется языком.
В GCC это поведение может быть воспроизведено на следующем минималистическом примере. Один исходный файл
#include <iostream>
void foo() __attribute__((weak));
void foo() { std::cout << "Hello!" << std::endl; }
Еще один исходный файл
#include <iostream>
void foo();
namespace N { using ::foo; }
void foo() { std::cout << "Goodbye!" << std::endl; }
int main()
{
foo();
N::foo();
}
В этом случае вы также заметите, что новое определение ::foo
("Goodbye!"
) во втором исходном файле также влияет на поведение N::foo
, Оба вызова будут выводить "Goodbye!"
, И если вы удалите определение ::foo
из второго исходного файла оба вызова будут отправлены в «оригинальное» определение ::foo
и вывод "Hello!"
,
Разрешение, данное вышеупомянутым 20.5.1.2/4, предназначено для упрощения реализации <cmath>
, Реализации разрешено просто включать в C-стиле <math.h>
, а затем повторно объявить функции в std
и добавить некоторые специфичные для C ++ дополнения и настройки. Если приведенное выше объяснение правильно описывает внутреннюю механику проблемы, то большая ее часть зависит от заменяемости слабых символов для Версии в стиле C функций.
Обратите внимание, что если мы просто глобально заменить int
с double
в приведенной выше программе код (под GCC) будет вести себя «как ожидалось» — он будет выводить -5 5
, Это происходит потому, что стандартная библиотека C не имеет abs(double)
функция. Объявив наш собственный abs(double)
Мы ничего не заменим.
Но если после переключения с int
с double
мы также переключаемся с abs
в fabs
, оригинальное странное поведение вновь появится во всей красе -5 -5
).
Это согласуется с приведенным выше объяснением.
Ваш код вызывает неопределенное поведение.
C ++ 17 [extern.names] / 4:
Каждая сигнатура функции из стандартной библиотеки C, объявленная с внешней связью, зарезервирована для реализации для использования в качестве сигнатуры функции как с внешней связью «C», так и с внешней связью «C ++», или в качестве имени области пространства имен в глобальном пространстве имен.
Таким образом, вы не можете создать функцию с тем же прототипом, что и стандартная библиотечная функция C. int abs(int);
, Независимо от того, какие заголовки вы на самом деле включаете или эти заголовки также помещают имена библиотек C в глобальное пространство имен.
Тем не менее, было бы разрешено перегрузить abs
если вы предоставляете разные типы параметров.