Ответ 1
Спецификация языка позволяет реализациям реализовывать <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-style <math.h>
, затем обновлять функции в std
и добавлять некоторые C++ -специальные дополнения и настройки. Если приведенное выше объяснение правильно описывает внутреннюю механику проблемы, то основная ее часть зависит от замены слабых символов для версий функций в стиле C.
Обратите внимание, что если мы просто глобально заменим int
на double
в вышеуказанной программе, код (под GCC) будет вести себя "как ожидалось" - он выведет -5 5
. Это происходит потому, что стандартная библиотека C не имеет функции abs(double)
. Объявив наш собственный abs(double)
, мы ничего не заменяем.
Но если после переключения из int
с double
мы также перейдем от abs
к fabs
, оригинальное странное поведение снова появится в его полной славе (вывод -5 -5
).
Это согласуется с приведенным выше объяснением.