Una dichiarazione può influire sullo spazio dei nomi 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;
}

Mi aspettavo che l'output fosse -5e 5, ma l'output è -5e -5.

Mi chiedo perché accadrà questo caso?

Ha qualcosa a che fare con l'uso di stdo cosa?


1
La tua implementazione di absnon è corretta.
Richard Critten

31
@RichardCritten Questo è il punto. OP sta chiedendo perché aggiungere questi absaffetti rotti std::abs().
HolyBlackCat

11
Interessante, ottengo 5e 5con clang -5e -5con gcc.
Rakete1111

10
Cmake non è un compilatore, ma piuttosto un sistema di compilazione. Puoi usare cmake per creare con vari compilatori.
HolyBlackCat

5
Probabilmente ti avrei consigliato di avere semplicemente la tua funzione return 0- questo avrebbe evitato che le persone pensassero che tu avessi implementato involontariamente la funzione in modo errato e reso più chiaro il comportamento desiderato ed effettivo.
Bernhard Barker

Risposte:


92

La specifica del linguaggio consente l' implementazione delle implementazioni <cmath>dichiarando (e definendo) le funzioni standard nello spazio dei nomi globale e quindi portandole nello spazio stddei nomi mediante dichiarazioni-utilizzo. Non è specificato se questo approccio viene utilizzato

20.5.1.2 Intestazioni
4 [...] Nella libreria standard C ++, tuttavia, le dichiarazioni (ad eccezione dei nomi che sono definiti come macro in C) rientrano nell'ambito dello spazio dei nomi (6.3.6) dello spazio dei nomi std. Non è specificato se questi nomi (inclusi eventuali sovraccarichi aggiunti nelle clausole da 21 a 33 e nell'Allegato D) siano prima dichiarati all'interno dell'ambito dello spazio dei nomi globale e siano poi iniettati nello spazio stddei nomi da dichiarazioni using esplicite (10.3.3).

Apparentemente, hai a che fare con una delle implementazioni che hanno deciso di seguire questo approccio (ad es. GCC). Cioè la tua implementazione fornisce ::abs, mentre std::abssemplicemente "si riferisce" a ::abs.

Una domanda che rimane in questo caso è perché oltre allo standard ::abssei stato in grado di dichiarare il tuo ::abs, cioè perché non c'è errore di definizione multipla. Ciò potrebbe essere causato da un'altra caratteristica fornita da alcune implementazioni (es. GCC): dichiarano le funzioni standard come i cosiddetti simboli deboli , consentendo così di "sostituirle" con le proprie definizioni.

Questi due fattori insieme creano l'effetto che osservi: la sostituzione del simbolo debole di ::abscomporta anche la sostituzione di std::abs. Quanto questo concordi con lo standard linguistico è un'altra storia ... In ogni caso, non fare affidamento su questo comportamento - non è garantito dalla lingua.

In GCC questo comportamento può essere riprodotto dal seguente esempio minimalista. Un file sorgente

#include <iostream>

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

Un altro file sorgente

#include <iostream>

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

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

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

In questo caso osserverai anche che la nuova definizione di ::foo( "Goodbye!") nel secondo file sorgente influenza anche il comportamento di N::foo. Entrambe le chiamate verranno emesse "Goodbye!". E se rimuovi la definizione di ::foodal secondo file sorgente, entrambe le chiamate verranno inviate alla definizione "originale" ::fooe all'output "Hello!".


L'autorizzazione data dal precedente 20.5.1.2/4 serve a semplificare l'implementazione di <cmath>. Le implementazioni possono includere semplicemente lo stile C <math.h>, quindi dichiarare nuovamente le funzioni stde aggiungere alcune aggiunte e modifiche specifiche per C ++. Se la spiegazione di cui sopra descrive adeguatamente i meccanismi interni del problema, una parte importante di essa dipende dalla sostituibilità dei simboli deboli per le versioni in stile C delle funzioni.

Nota che se sostituiamo globalmente intcon doublenel programma precedente, il codice (sotto GCC) si comporterà "come previsto" - verrà visualizzato -5 5. Ciò accade perché la libreria standard C non ha abs(double)funzioni. Dichiarando il nostro abs(double), non sostituiamo nulla.

Ma se dopo il passaggio da intcon doublesi passa anche da absa fabs, il comportamento strano originale riapparirà nella sua piena gloria (output -5 -5).

Ciò è coerente con la spiegazione di cui sopra.


come posso vedere nel sorgente di cmath non c'è using ::abs;simile per using ::asin;quindi puoi sovrascrivere la dichiarazione, un altro punto da menzionare è che le funzioni definite nello spazio dei nomi std non sono dichiarate per int ma piuttosto per double , float
Take_Care_

2
Dal punto di vista dello standard, il comportamento non è definito per [extern.names] / 4 .
xskxzr

Ma quando ho cancellato il #include<cmath>nel mio codice, ho ottenuto la stessa risposta.
Peter

@ Peter Ma allora da dove prendi std :: abs? - Potrebbe essere stato incluso tramite un altro include, a quel punto sei tornato a questa spiegazione. (Non importa al compilatore se un'intestazione è inclusa direttamente o indirettamente.)
RM

@Peter: abspuò essere dichiarato <cstdlib>anche in, che potrebbe essere implicitamente incluso tramite <iostream>. Prova a rimuovere il tuo abse vedi se si compila ancora.
AnT

13

Il tuo codice causa un comportamento indefinito.

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

Ogni firma di funzione dalla libreria standard C dichiarata con collegamento esterno è riservata all'implementazione per l'uso come firma di funzione con collegamento sia esterno "C" che esterno "C ++" o come nome dell'ambito dello spazio dei nomi nello spazio dei nomi globale.

Quindi non è possibile creare una funzione con lo stesso prototipo della funzione di libreria C standard int abs(int);. Indipendentemente da quali intestazioni includi effettivamente o se queste intestazioni inseriscano anche i nomi delle librerie C nello spazio dei nomi globale.

Tuttavia, sarebbe consentito il sovraccarico abs se si forniscono diversi tipi di parametri.


1
"o come nome dell'ambito dello spazio dei nomi nello spazio dei nomi globale", quindi non può essere sovraccaricato nello spazio dei nomi globale.
xskxzr

@xskxzr Non sono sicuro dell'interpretazione del testo che citi; se si intende che l'utente non può dichiarare nulla con quel nome nello spazio dei nomi globale, la parte precedente del testo che ho citato sarebbe ridondante, come la maggior parte di [extern.names] / 3. Il che mi porta a pensare che qui fosse inteso qualcos'altro.
MM
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.