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?
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 constexpr
peut ê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.
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.
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.
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);
};
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
ouconstexpr
et ni explicitement déclaréeextern
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.
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
.)