web-dev-qa-db-fra.com

Erreur de l'éditeur de liens C ++ avec la classe constexpr statique

Je compile le programme simple suivant avec g++-4.6.1 --std=c++0x:

#include <algorithm>

struct S
{
    static constexpr int X = 10;
};

int main()
{
    return std::min(S::X, 0);
};

J'obtiens l'erreur de l'éditeur de liens suivante:

/tmp/ccBj7UBt.o: In function `main':
scratch.cpp:(.text+0x17): undefined reference to `S::X'
collect2: ld returned 1 exit status

Je me rends compte que les membres statiques définis en ligne n'ont pas de symboles définis, mais j'avais l'impression (probablement imparfaite) que l'utilisation de constexpr indiquait au compilateur de toujours traiter le symbole comme une expression; ainsi, le compilateur saurait qu'il n'est pas légal de passer une référence au symbole S::X (pour la même raison, vous ne pouvez pas faire référence au littéral 10).

Cependant, si S est déclaré comme espace de noms, c'est-à-dire "espace de noms S" au lieu de "struct S", tout est bien lié.

Est-ce un g++ bug ou dois-je encore utiliser une astuce pour contourner ce désagrément?

48
Travis Gockel

Je ne pense pas que ce soit un bug. Si vous changez le constexpr en const, il échoue toujours, avec exactement la même erreur.

Vous avez déclaré S::X, mais pas défini nulle part, donc il n'y a pas de stockage pour cela. Si vous faites quoi que ce soit qui nécessite de connaître son adresse, vous devrez également le définir quelque part.

Exemples:

int main() {
      int i = S::X; // fine
      foo<S::X>(); // fine
      const int *p = &S::X; // needs definition
      return std::min(S::X, 0); // needs it also
}

La raison en est que constexprpeut être évalué au moment de la compilation, mais ce n'est pas obligatoire être évalué en tant que tel, et peut également se produire au moment de l'exécution. Il n'instruit pas "le compilateur à toujours traiter le symbole comme une expression", il laisse entendre qu'il serait raisonnable et permis de le faire si le compilateur en avait envie.

32
Flexo

La raison de l'erreur a déjà été expliquée, je voudrais donc simplement ajouter une solution de contournement.

return std::min(int(S::X), 0);

Cela crée un temporaire, donc std::min pourrait y faire référence.

12
VladV

Cela a été corrigé dans C++ 17.

https://en.cppreference.com/w/cpp/language/static :

Si un membre de données statiques est déclaré constexpr, il est implicitement en ligne et n'a pas besoin d'être redéclaré à la portée de l'espace de noms. Cette redéclaration sans initialiseur (auparavant requise comme indiqué ci-dessus) est toujours autorisée, mais elle est déconseillée.

8
Trass3r

Vous devez également fournir une définition pour le membre constexpr en dehors de la structure (ou classe), mais cette fois sans sa valeur. Voir ici: https://en.cppreference.com/w/cpp/language/static

#include <algorithm>

struct S
{
    static constexpr int X = 10;
};

constexpr int S::X;

int main()
{
    return std::min(S::X, 0);
};
6
jciloa

Dans la norme C++ ( dernière version de travail ), il est dit:

Un nom ayant une étendue d'espace de noms (3.3.6) a un lien interne s'il s'agit du nom d'une [...] variable explicitement déclarée const ou constexpr et ni explicitement déclarée extern ni déclaré précédemment avoir un lien externe [...].

"Linkage" est défini comme ceci:

Un nom est censé avoir un lien lorsqu'il peut désigner le même objet, référence, fonction, type, modèle, espace de noms ou valeur qu'un nom introduit par une déclaration dans une autre portée:

- Lorsqu'un nom a une liaison externe , l'entité qu'il désigne peut être désignée par des noms provenant de portées d'autres unités de traduction ou d'autres portées de la même traduction. unité.

- Lorsqu'un nom a une liaison interne , l'entité qu'il désigne peut être désignée par des noms provenant d'autres étendues dans la même unité de traduction.

- Lorsqu'un nom n'a aucun lien , l'entité qu'il désigne ne peut pas être désignée par des noms provenant d'autres étendues.

Ainsi, en cas de namespace S, il aura une liaison externe , en cas de struct S, il aura une liaison interne .

Les symboles avec liaison externe doivent avoir le symbole défini explicitement dans une unité de traduction.

5
Albert

Votre compréhension de constexpr est incorrecte. Une lvalue déclarée constexpr est toujours une lvalue, et une fonction déclarée constexpr est toujours une fonction. Et lorsqu'une fonction a un paramètre de référence et qu'une valeur lval lui est transmise, le langage requiert que la référence fasse référence à cette valeur l et rien d'autre. (Lorsqu'il est appliqué à une variable de type int, il y a vraiment très peu de différence entre constexpr et plain const.)

1
James Kanze