Специфікація мови дозволяє реалізаціям реалізовувати <cmath>
шляхом декларування (та визначення) стандартних функцій у глобальному просторі імен, а потім перенесення їх у простір імен std
за допомогою декларацій use. Не визначено, чи застосовується такий підхід
20.5.1.2 Заголовки
4 [...] У стандартній бібліотеці С ++, однак, оголошення (за винятком імен, які визначені як макроси в С) знаходяться в межах простору імен (6.3.6) простору імен std
. Не вказано, чи спочатку ці імена (включаючи будь-які перевантаження, додані в пунктах 21 - 33 та Додатку D) спочатку оголошуються в межах загального простору імен, а потім вводяться у простір імен std
за допомогою явних декларацій use (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 ++. Якщо вищевказане пояснення належним чином описує внутрішню механіку проблеми, то основна частина цього залежить від заміни слабких символів для версій функцій у стилі С.
Зверніть увагу, що якщо ми просто глобально замінимо int
на double
у наведеній вище програмі, код (згідно з GCC) буде поводитися "як очікувалося" - він виведе -5 5
. Це трапляється, оскільки стандартна бібліотека C не має abs(double)
функції. Декларуючи своє abs(double)
, ми нічого не замінюємо.
Але якщо після перемикання з за int
допомогою double
ми також перемикаємося з abs
на fabs
, оригінальна дивна поведінка знову з’явиться у повній красі (вихід -5 -5
).
Це узгоджується з наведеним вище поясненням.
abs
неправильна.