Czy deklaracja może wpływać na przestrzeń nazw 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;
}

Spodziewałem się, że wyjście będzie -5i 5, ale wyjście to -5i -5.

Zastanawiam się, dlaczego tak się stanie?

Czy ma to coś wspólnego z użyciem stdczy co?

Piotr
źródło
1
Twoja implementacja absjest nieprawidłowa.
Richard Critten
31
@RichardCritten O to chodzi. OP pyta, dlaczego dodanie tego zepsutego abswpływa std::abs().
HolyBlackCat
11
Ciekawe, dostaję 5i 5z clangiem -5iz -5gcc.
Rakete1111
10
Cmake nie jest kompilatorem, ale raczej systemem kompilacji. Możesz użyć cmake do kompilacji z różnymi kompilatorami.
HolyBlackCat
5
Prawdopodobnie zaleciłbym, abyś po prostu posiadał swoją funkcję return 0- dzięki temu ludzie nie myśleliby, że nieumyślnie zaimplementowałeś funkcję nieprawidłowo, a pożądane i rzeczywiste zachowanie stałoby się wyraźniejsze.
Bernhard Barker

Odpowiedzi:

92

Specyfikacja języka umożliwia implementację implementacji <cmath>poprzez deklarowanie (i definiowanie) standardowych funkcji w globalnej przestrzeni nazw, a następnie przenoszenie ich do przestrzeni nazw stdza pomocą deklaracji using. Nie określono, czy takie podejście jest stosowane

20.5.1.2 Nagłówki
4 [...] Jednak w standardowej bibliotece C ++ deklaracje (z wyjątkiem nazw, które są zdefiniowane jako makra w C) mieszczą się w zakresie przestrzeni nazw (6.3.6) tej przestrzeni nazw std. Nie jest określone, czy te nazwy (w tym wszelkie przeciążenia dodane w klauzulach od 21 do 33 i załączniku D) są najpierw deklarowane w zakresie globalnej przestrzeni nazw, a następnie są wprowadzane do przestrzeni nazw stdprzez jawne deklaracje użycia (10.3.3).

Najwyraźniej masz do czynienia z jedną z implementacji, która zdecydowała się na takie podejście (np. GCC). Oznacza to, że Twoja implementacja zapewnia ::abs, a std::abspo prostu „odnosi się” do ::abs.

Pytanie, które pozostaje w tym przypadku, to dlaczego oprócz standardu ::absmogłeś zadeklarować swój własny ::abs, tj. Dlaczego nie ma błędu wielokrotnej definicji. Może to być spowodowane inną cechą udostępnianą przez niektóre implementacje (np. GCC): deklarują one standardowe funkcje jako tzw. Słabe symbole , dzięki czemu można je „zastąpić” własnymi definicjami.

Te dwa czynniki razem tworzą efekt, który obserwujesz: słaba zamiana symbolu ::abspowoduje również zastąpienie std::abs. Jak dobrze to zgadza się ze standardem językowym, to inna historia… W każdym razie nie polegaj na tym zachowaniu - nie gwarantuje tego język.

W GCC to zachowanie można odtworzyć na następującym minimalistycznym przykładzie. Jeden plik źródłowy

#include <iostream>

void foo() __attribute__((weak));
void foo() { std::cout << "Hello!" << std::endl; }

Inny plik źródłowy

#include <iostream>

void foo();
namespace N { using ::foo; }

void foo() { std::cout << "Goodbye!" << std::endl; }

int main()
{
  foo();
  N::foo();
}

W takim przypadku zauważysz również, że nowa definicja ::foo( "Goodbye!") w drugim pliku źródłowym również wpływa na zachowanie N::foo. Zostaną wyświetlone oba wywołania "Goodbye!". A jeśli usuniesz definicję ::fooz drugiego pliku źródłowego, oba wywołania będą kierowane do „oryginalnej” definicji::foo i danych wyjściowych "Hello!".


Zezwolenie udzielone przez powyższy 20.5.1.2/4 ma na celu uproszczenie implementacji <cmath>. Implementacje mogą po prostu zawierać styl C <math.h>, a następnie ponownie zadeklarować funkcje stdi dodać niektóre C ++ - specyficzne dodatki i poprawki. Jeśli powyższe wyjaśnienie właściwie opisuje wewnętrzną mechanikę problemu, to większa jego część zależy od zastępowalności słabych symboli na wersji funkcji w stylu C.

Zauważ, że jeśli po prostu globalnie zastąpić intze doublew powyższym programie, kod (pod GCC) będą zachowywać się „jak oczekiwano” - to będzie wyjście -5 5. Dzieje się tak, ponieważ standardowa biblioteka C nie ma abs(double)funkcji. Deklarując własne abs(double), niczego nie zastępujemy.

Ale jeśli po przełączeniu z intwith doublerównież zmienimy się z absna fabs, oryginalne dziwne zachowanie pojawi się ponownie w pełnej okazałości (wyjście -5 -5).

Jest to zgodne z powyższym wyjaśnieniem.

Mrówka
źródło
jak widzę w źródle cmath nie ma czegoś using ::abs;podobnego do using ::asin;więc możesz przesłonić deklarację, kolejną kwestią, o której warto wspomnieć, jest to, że zdefiniowane w std funkcje przestrzeni nazw nie są zadeklarowane dla int, ale raczej dla double , float
Take_Care_
2
Z punktu widzenia normy zachowanie jest niezdefiniowane według [extern.names] / 4 .
xskxzr
Ale kiedy #include<cmath>usunąłem w moim kodzie, otrzymałem tę samą odpowiedź. ”
Peter
@Peter Ale w takim razie skąd bierzesz std :: abs? - Może być uwzględniony za pośrednictwem innego dołączenia, w którym to momencie wracasz do tego wyjaśnienia. (Dla kompilatora nie ma znaczenia, czy nagłówek jest dołączony bezpośrednio, czy pośrednio.)
RM
@Peter: absmożna również zadeklarować w <cstdlib>, co może być niejawnie włączone za pośrednictwem <iostream>. Spróbuj usunąć własne absi zobacz, czy nadal się kompiluje.
AnT
13

Twój kod powoduje niezdefiniowane zachowanie.

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

Każda sygnatura funkcji ze standardowej biblioteki C zadeklarowana z łączeniem zewnętrznym jest zarezerwowana dla implementacji do użycia jako sygnatura funkcji z połączeniem zewnętrznym „C” i zewnętrznym „C ++” lub jako nazwa zakresu przestrzeni nazw w globalnej przestrzeni nazw.

Nie możesz więc utworzyć funkcji z tym samym prototypem, co funkcja standardowej biblioteki C. int abs(int); . Bez względu na to, które nagłówki faktycznie dołączasz, lub czy te nagłówki również umieszczają nazwy bibliotek C w globalnej przestrzeni nazw.

Jednak byłoby dozwolone przeciążenie, absjeśli podasz różne typy parametrów.

MM
źródło
1
„lub jako nazwa zakresu przestrzeni nazw w globalnej przestrzeni nazw”, więc nie może być przeciążony w globalnej przestrzeni nazw.
xskxzr
@xskxzr Nie jestem pewien co do interpretacji cytowanego tekstu; jeśli przyjmiemy, że użytkownik nie może zadeklarować niczego o tej nazwie w globalnej przestrzeni nazw, to poprzednia część cytowanego przeze mnie tekstu byłaby zbędna, podobnie jak większość [extern.names] / 3. Co prowadzi mnie do wniosku, że chodziło tu o coś innego.
MM