Может ли объявление повлиять на пространство имен std?

#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 или что?

Ответ 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).

Это согласуется с приведенным выше объяснением.

Ответ 2

Ваш код вызывает неопределенное поведение.

C++ 17 [extern.names]/4:

Каждая сигнатура функции из стандартной библиотеки C, объявленная с внешней связью, зарезервирована для реализации для использования в качестве сигнатуры функции с привязкой extern "C" и "extern" C++ или как имя области пространства имен в глобальном пространстве имен.

Таким образом, вы не можете создать функцию с тем же прототипом, что и библиотека стандартного C int abs(int); , Независимо от того, какие заголовки вы действительно включаете, или же эти заголовки также помещают имена библиотек C в глобальное пространство имен.

Однако, если вы предоставляете разные типы параметров, будет разрешено перегружать abs.