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

96
#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
Ваша реализация absневерна.
Ричард Криттен
31
@RichardCritten В том-то и дело. ОП спрашивает, почему добавление этого сломанного absаффекта std::abs().
HolyBlackCat
11
Интересно, у меня получается 5и 5с clang, -5и -5с gcc.
Rakete1111
10
Cmake - это не компилятор, а система сборки. Вы можете использовать cmake для сборки с различными компиляторами.
HolyBlackCat
5
Я, вероятно, порекомендовал бы вам просто иметь свою функцию return 0- это позволило бы избежать того, чтобы люди думали, что вы непреднамеренно реализовали функцию неправильно, и сделало бы желаемое и фактическое поведение более ясным.
Бернхард Баркер

Ответы:

92

Спецификация языка позволяет реализовать реализации <cmath>, объявляя (и определяя) стандартные функции в глобальном пространстве имен, а затем помещая их в пространство имен stdс помощью объявлений-использования. Не указано, используется ли этот подход.

20.5.1.2 Заголовки
4 [...] Однако в стандартной библиотеке C ++ объявления (за исключением имен, которые определены как макросы в C) находятся в области видимости пространства имен (6.3.6) пространства имен std. Не указано, были ли эти имена (включая любые перегрузки, добавленные в разделах 21–33 и приложении D) сначала объявлены в области глобального пространства имен, а затем введены в пространство имен stdявными объявлениями using (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), мы ничего не заменяем.

Но если после переключения с intwith doubleмы также переключимся с absна fabs, исходное странное поведение снова проявится во всей красе (результат -5 -5).

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

Муравей
источник
как я вижу в источнике cmath, нет ничего using ::abs;подобного, using ::asin;поэтому вы можете переопределить объявление, еще один момент, который следует упомянуть, заключается в том, что функции, определенные в пространстве имен std, объявлены не для int, а для double , float
Take_Care_
2
С точки зрения стандарта поведение не определено для [extern.names] / 4 .
xskxzr
Но когда я удалил #include<cmath>в своем коде, я получил тот же ответ »
Питер
@Peter Но тогда откуда вы берете std :: abs? - Он может быть включен через другое включение, после чего вы вернетесь к этому объяснению. (Для компилятора не имеет значения, включен ли заголовок прямо или косвенно.)
РМ
@Peter: также absможет быть объявлен в <cstdlib>, что может быть неявно включено через <iostream>. Попробуйте удалить свой собственный absи посмотреть, компилируется ли он.
AnT
13

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

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

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

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

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

ММ
источник
1
«или как имя области видимости пространства имен в глобальном пространстве имен», поэтому его нельзя перегрузить в глобальном пространстве имен.
xskxzr
@xskxzr Я не уверен в интерпретации цитируемого вами текста; если это означает, что пользователь не может объявить что-либо с этим именем в глобальном пространстве имен, то предыдущая часть цитируемого мной текста будет избыточной, как и большая часть [extern.names] / 3. Это наводит меня на мысль, что здесь было задумано нечто иное.
MM