Ответ 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).
Это согласуется с приведенным выше объяснением.