Référence indéfinie à statique const int

J’ai rencontré une question intéressante aujourd’hui. Considérons cet exemple simple:

template  void foo(const T & a) { /* code */ } // This would also fail // void foo(const int & a) { /* code */ } class Bar { public: static const int kConst = 1; void func() { foo(kConst); // This is the important line } }; int main() { Bar b; b.func(); } 

Lors de la compilation, je reçois une erreur:

 Undefined reference to 'Bar::kConst' 

Maintenant, je suis sûr que c’est parce que le static const int n’est pas défini n’importe où, ce qui est intentionnel car d’après ce que j’ai compris, le compilateur devrait être capable de le remplacer au moment de la compilation sans avoir besoin de définition. Cependant, comme la fonction prend un paramètre const int & , il semble qu’elle ne fasse pas la substitution et préfère plutôt une référence. Je peux résoudre ce problème en apportant les modifications suivantes:

 foo(static_cast(kConst)); 

Je crois que cela force maintenant le compilateur à créer un int temporaire, puis à lui transmettre une référence, ce qu’il peut faire avec succès à la compilation.

Je me demandais si c’était intentionnel ou est-ce que j’attendais trop de la part de gcc pour pouvoir gérer cette affaire? Ou est-ce quelque chose que je ne devrais pas faire pour une raison quelconque?

C’est intentionnel, 9.4.2 / 4 dit:

Si un membre de données statique est de type const integr ou const énumération, sa déclaration dans la définition de classe peut spécifier un initialiseur constant qui doit être une expression constante intégrale (5.19). Dans ce cas, le membre peut apparaître dans des expressions constantes intégrales. Le membre doit toujours être défini dans une étendue d’espace de noms s’il est utilisé dans le programme

Lorsque vous transmettez le membre de données statique par référence const, vous l’utilisez “3.2 / 2”:

Une expression est potentiellement évaluée à moins qu’elle n’apparaisse lorsqu’une expression constante intégrale est requirejse (voir 5.19), est l’opérande de l’opérateur sizeof (5.3.3), ou est l’opérande de l’opérateur typographique et l’expression ne désigne pas type de classe polymorphe (5.2.8). Un object ou une fonction non surchargée est utilisé si son nom apparaît dans une expression potentiellement évaluée.

Donc, en fait, vous “l’utilisez” lorsque vous le passez par valeur, ou dans un static_cast . C’est juste que GCC vous a laissé partir dans un cas mais pas dans l’autre.

[Edit: gcc applique les règles des brouillons C ++ 0x: “Une variable ou une fonction non surchargée dont le nom apparaît en tant qu’expression potentiellement évaluée est odr-used sauf s’il s’agit d’un object qui satisfait aux conditions requirejses pour apparaître dans une constante expression (5.19) et la conversion lvalue-à-valeur (4.1) sont immédiatement appliquées. “. La dissortingbution statique effectue la conversion lvalue-rvalue immédiatement, donc en C ++ 0x, elle n’est pas “utilisée”.]

Le problème pratique de la référence const est que foo le droit de prendre l’adresse de son argument et de la comparer, par exemple, à l’adresse de l’argument d’un autre appel, stocké dans un global. Comme un membre de données statique est un object unique, cela signifie que si vous appelez foo(kConst) partir de deux foo(kConst) différentes, l’adresse de l’object passé doit être identique dans chaque cas. AFAIK GCC ne peut organiser cela que si l’object est défini dans une (et une seule) TU.

OK, alors dans ce cas, foo est un template, donc la définition est visible dans tous les TU, donc le compilateur pourrait en théorie exclure le risque qu’il fasse quoi que ce soit avec l’adresse. Mais en général, vous ne devriez certainement pas prendre d’adresse ou de référence à des objects inexistants 😉

Si vous écrivez une variable const statique avec l’initialiseur dans la déclaration de classe, c’est comme si vous aviez écrit

 class Bar { enum { kConst = 1 }; } 

et GCC le traitera de la même manière, ce qui signifie qu’il n’a pas d’adresse.

Le code correct doit être

 class Bar { static const int kConst; } const int Bar::kConst = 1; 

C’est un cas vraiment valable. Surtout parce que foo pourrait être une fonction de la STL comme std :: count qui prend un const T & comme troisième argument.

J’ai passé beaucoup de temps à essayer de comprendre pourquoi l’éditeur de liens avait des problèmes avec un code aussi basique.

Le message d’erreur

Référence indéfinie à ‘Bar :: kConst’

nous dit que l’éditeur de liens ne peut pas trouver de symbole.

 $nm -C main.o 0000000000000000 T main 0000000000000000 W void foo(int const&) 0000000000000000 W Bar::func() 0000000000000000 U Bar::kConst 

Nous pouvons voir à partir du «U» que Bar :: kConst est indéfini. Par conséquent, lorsque l’éditeur de liens tente de faire son travail, il doit trouver le symbole. Mais vous ne déclarez que kConst et ne le définissez pas.

La solution en C ++ est également de le définir comme suit:

 template  void foo(const T & a) { /* code */ } class Bar { public: static const int kConst = 1; void func() { foo(kConst); // This is the important line } }; const int Bar::kConst; // Definition < --FIX int main() { Bar b; b.func(); } 

Ensuite, vous pouvez voir que le compilateur placera la définition dans le fichier object généré:

 $nm -C main.o 0000000000000000 T main 0000000000000000 W void foo(int const&) 0000000000000000 W Bar::func() 0000000000000000 R Bar::kConst 

Maintenant, vous pouvez voir le «R» indiquant qu'il est défini dans la section des données.

g ++ version 4.3.4 accepte ce code (voir ce lien ). Mais g ++ version 4.4.0 le rejette.

Je pense que cet artefact de C ++ signifie que chaque fois que Bar::kConst est mentionné, sa valeur littérale est utilisée à la place.

Cela signifie que, dans la pratique, il n’ya pas de variable à laquelle faire référence.

Vous devrez peut-être faire ceci:

 void func() { int k = kConst; foo(k); } 

kConst simple: utilisez + avant que kConst transmette la fonction. Cela évitera que la constante soit prise comme référence et, de cette façon, le code ne générera pas de requête de l’éditeur de liens vers l’object constant, mais passera à la valeur constante du compilateur à la place.

Vous pouvez également le remplacer par une fonction membre constexpr:

 class Bar { static constexpr int kConst() { return 1; }; }; 

J’ai rencontré le même problème que mentionné par Cloderic (const statique dans un opérateur ternaire: r = s ? kConst1 : kConst2 ), mais il ne s’est plaint qu’après avoir désactivé l’optimisation du compilateur (-O0 au lieu de -Os). Arrivé sur gcc-none-eabi 4.8.5.