Une déclaration peut-elle affecter l'espace de noms 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;
}

Je m'attendais à ce que la sortie soit -5et 5, mais la sortie est le -5et -5.

Je me demande pourquoi ce cas se produira?

Cela a-t-il quelque chose à voir avec l'utilisation stdou quoi?

Peter
la source
1
Votre implémentation de absest incorrecte.
Richard Critten
31
@RichardCritten C'est le point. OP demande pourquoi l'ajout de ces abseffets brisés std::abs().
HolyBlackCat
11
Intéressant, je reçois 5et 5avec clang, -5et -5avec gcc.
Rakete1111
10
Cmake n'est pas un compilateur, mais plutôt un système de construction. Vous pouvez utiliser cmake pour construire avec différents compilateurs.
HolyBlackCat
5
J'aurais probablement recommandé que vous ayez simplement votre fonction return 0- cela aurait évité que les gens pensent que vous avez involontairement implémenté la fonction de manière incorrecte et clarifié le comportement souhaité et réel.
Bernhard Barker

Réponses:

92

La spécification du langage permet aux implémentations d'implémenter <cmath>en déclarant (et en définissant) les fonctions standard dans l' espace de noms global , puis en les amenant dans l'espace stdde noms au moyen de déclarations d'utilisation. Il n'est pas précisé si cette approche est utilisée

20.5.1.2 En-têtes
4 [...] Dans la bibliothèque standard C ++, cependant, les déclarations (à l'exception des noms qui sont définis comme des macros en C) sont dans la portée de l'espace de noms (6.3.6) de l'espace de noms std. Il n'est pas précisé si ces noms (y compris les surcharges ajoutées dans les Articles 21 à 33 et dans l'Annexe D) sont d'abord déclarés dans la portée de l'espace de noms global et sont ensuite injectés dans l'espace stdde noms par des déclarations d'utilisation explicites (10.3.3).

Apparemment, vous avez affaire à l'une des implémentations qui a décidé de suivre cette approche (par exemple GCC). Ie votre mise en œuvre fournit ::abs, tout std::abssimplement "se réfère" à ::abs.

Une question qui demeure dans ce cas est de savoir pourquoi en plus du standard ::absvous avez pu déclarer le vôtre ::abs, c'est-à-dire pourquoi il n'y a pas d'erreur de définition multiple. Cela peut être causé par une autre fonctionnalité fournie par certaines implémentations (par exemple GCC): elles déclarent des fonctions standard comme des symboles dits faibles , vous permettant ainsi de les "remplacer" par vos propres définitions.

Ensemble, ces deux facteurs créent l'effet que vous observez: le remplacement d'un symbole faible de ::absentraîne également le remplacement de std::abs. À quel point cela s'accorde avec la norme de la langue est une autre histoire ... Dans tous les cas, ne vous fiez pas à ce comportement - il n'est pas garanti par la langue.

Dans GCC, ce comportement peut être reproduit par l'exemple minimaliste suivant. Un fichier source

#include <iostream>

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

Un autre fichier source

#include <iostream>

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

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

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

Dans ce cas, vous observerez également que la nouvelle définition de ::foo( "Goodbye!") dans le deuxième fichier source affecte également le comportement de N::foo. Les deux appels seront émis "Goodbye!". Et si vous supprimez la définition de ::foodu deuxième fichier source, les deux appels seront envoyés à la définition «d'origine» ::fooet à la sortie "Hello!".


L'autorisation donnée par le 20.5.1.2/4 ci-dessus est là pour simplifier la mise en œuvre de <cmath>. Les implémentations sont autorisées à simplement inclure le style C <math.h>, puis à redéclarer les fonctions stdet à ajouter des ajouts et des modifications spécifiques à C ++. Si l'explication ci-dessus décrit correctement la mécanique interne du problème, alors une grande partie de celui-ci dépend de la remplaçabilité des symboles faibles pour les versions de style C des fonctions.

Notez que si nous remplaçons simplement globalement intpar doubledans le programme ci-dessus, le code (sous GCC) se comportera "comme prévu" - il sortira -5 5. Cela se produit parce que la bibliothèque standard C n'a pas de abs(double)fonction. En déclarant le nôtre abs(double), nous ne remplaçons rien.

Mais si, après le passage de intavec, doublenous passons également de absà fabs, le comportement étrange d'origine réapparaîtra dans toute sa splendeur (sortie -5 -5).

Ceci est cohérent avec l'explication ci-dessus.

Fourmi
la source
comme je peux le voir dans la source de cmath, il n'y a pas de using ::abs;pareil pour le using ::asin;donc Vous pouvez remplacer la déclaration, un autre point à mentionner est que les fonctions d'espace de noms définies dans std ne sont pas déclarées pour int mais plutôt pour double , float
Take_Care_
2
Du point de vue de la norme, le comportement est indéfini par [extern.names] / 4 .
xskxzr
Mais quand j'ai supprimé le #include<cmath>dans mon code, j'ai eu la même réponse. »
Peter
@Peter Mais alors d'où vient std :: abs? - Il peut être inclus via un autre include, à quel point vous revenez à cette explication. (Cela n'a pas d'importance pour le compilateur si un en-tête est inclus directement ou indirectement.)
RM
@Peter: abspeut également être déclaré dans <cstdlib>, ce qui peut être implicitement inclus via <iostream>. Essayez de supprimer le vôtre abset de voir s'il se compile toujours.
Du
13

Votre code provoque un comportement indéfini.

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

Chaque signature de fonction de la bibliothèque standard C déclarée avec un lien externe est réservée à l'implémentation pour être utilisée comme signature de fonction avec une liaison à la fois externe "C" et externe "C ++", ou comme nom de portée de l'espace de noms dans l'espace de noms global.

Vous ne pouvez donc pas faire fonction avec le même prototype que la fonction de la bibliothèque standard C int abs(int);. Quels que soient les en-têtes que vous incluez réellement ou si ces en-têtes placent également les noms de bibliothèques C dans l'espace de noms global.

Cependant, il serait autorisé à surcharger abssi vous fournissez différents types de paramètres.

MM
la source
1
"ou en tant que nom de la portée de l'espace de noms dans l'espace de noms global", il ne peut donc pas être surchargé dans l'espace de noms global.
xskxzr
@xskxzr Je ne suis pas sûr de l'interprétation du texte que vous citez; si cela signifie que l'utilisateur ne peut rien déclarer de ce nom dans l'espace de noms global, alors la partie précédente du texte que j'ai citée serait redondante, comme le ferait la plupart de [extern.names] / 3. Ce qui m'amène à penser que quelque chose d'autre était prévu ici.
MM