web-dev-qa-db-fra.com

Référence non définie à la constante statique constexpr char []

Je veux avoir un static constchar tableau dans ma classe. GCC s'est plaint et m'a dit que je devrais utiliser constexpr, bien que maintenant on me dit que c'est une référence non définie. Si je fais du tableau un non-membre, alors il compile. Que se passe-t-il?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}
164
Pubby

Ajoutez à votre fichier cpp:

constexpr char foo::baz[];

Raison: vous devez fournir le définition du membre statique ainsi que la déclaration. La déclaration et l'initialiseur entrent dans la définition de la classe, mais la définition du membre doit être séparée.

159
Kerrek SB

C++ 17 introduit les variables en ligne

C++ 17 corrige ce problème pour les variables de membre statique constexpr nécessitant une définition hors ligne si elle était utilisée par ord. Voir la réponse originale ci-dessous pour plus de détails avant C++ 17.

Proposition P0386 Variables en ligne introduit la possibilité d'appliquer le spécificateur en-ligne aux variables. En particulier, constexpr implique inline pour les variables membres statiques. La proposition dit:

Le spécificateur en ligne peut être appliqué aux variables ainsi qu'aux fonctions. Une variable déclarée en ligne a la même sémantique qu'une fonction déclarée en ligne: elle peut être définie, à l'identique, en plusieurs unités de traduction, elle doit être définie dans chaque unité de traduction dans laquelle elle est odrusé, et le comportement du programme est comme s'il existait exactement une variable.

et modifié [basic.def] p2:

Une déclaration est une définition sauf si
...

  • il déclare un membre de données statique en dehors d'une définition de classe et la variable a été définie dans la classe avec le spécificateur constexpr (cet usage est obsolète; voir [depr.static_constexpr]),

...

et ajoutez [depr.static_constexpr] :

Pour assurer la compatibilité avec les normes internationales C++ antérieures, un membre de données statique constexpr peut être redéclaré de manière redondante en dehors de la classe sans initialiseur. Cet usage est déconseillé. [ Exemple:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

- fin exemple]

Réponse originale

En C++ 03, nous n’avions le droit de fournir en classe que des intialiseurs pour intégrales const ou types d’énumération const, en C++ 11 avec constexpr cela a été étendu à types littéraux.

En C++ 11, il n'est pas nécessaire de fournir une définition de la portée d'un espace de noms pour un membre statique constexpr si ce n'est pas odr-used, nous pouvons le voir dans le projet de section standard C++ 11 9.4.2 _ [class.static.data] qui dit ( c'est moi qui souligne):

[...] Un membre de données statique de type littéral peut être déclaré dans la définition de classe avec le spécificateur constexpr; Si tel est le cas, sa déclaration doit spécifier un initialisateur de type accolade ou égal dans lequel chaque clause d'initialisation qui est une expression d'affectation est une expression constante. [Remarque: dans les deux cas, le membre peut apparaître dans des expressions constantes. —End note] Le membre doit toujours être défini dans une portée d'espace de noms s'il est utilisé par odr (3.2) dans le programme et la définition de la portée d'espace de noms doit ne contient pas d'initialiseur.

La question devient alors la suivante: baz odr-used ici:

std::string str(baz); 

et la réponse est yes, nous avons donc également besoin d'une définition de la portée de l'espace de noms.

Alors, comment pouvons-nous déterminer si une variable est odr-used? Le texte original de C++ 11 dans la section 3.2 [basic.def.odr] dit:

Une expression est potentiellement évaluée sauf s'il s'agit d'un opérande non évalué (clause 5) ou d'une de ses sous-expressions. Une variable dont le nom apparaît en tant qu’expression potentiellement évaluée est odr-utilisée sauf si c’est un objet qui satisfait à la conditions requises pour apparaître dans une expression constante (5.19) et la conversion de lvalue en valeur (4.1) est immédiatement appliquée .

Donc, baz donne un expression constante mais la conversion lvalue-à-rvalue n'est pas immédiatement appliquée car elle n'est pas applicable en raison de baz étant un tableau. Ceci est couvert dans la section 4.1 [conv.lval] qui dit:

Une glvalue (3.10) d'une non-fonction, du type non-tableau T peut être convertie en prvalue.53 [...]

Ce qui est appliqué dans la conversion de tableau à pointeur.

Cette formulation de [basic.def.odr] a été modifiée en raison de rapport de défauts 712 car certains cas n'étaient pas couverts par cette formulation, mais ces modifications ne modifient pas les résultats. pour ce cas.

62
Shafik Yaghmour

C’est vraiment une faille dans C++ 11 - comme d’autres l’ont expliqué, en C++ 11, une variable membre statique de constexpr, contrairement à tous les autres types de variable globale constexpr, possède une liaison externe et doit donc être explicitement définie quelque part.

Il est également intéressant de noter que, lors de la compilation avec optimisation, vous pouvez souvent utiliser des variables membres constexpr statiques sans définitions, car elles peuvent se retrouver en ligne dans toutes les utilisations, mais si vous compilez sans optimisation, votre programme échouera souvent. Cela en fait un piège caché très courant - votre programme compile bien avec optimisation, mais dès que vous désactivez l'optimisation (peut-être pour le débogage), la liaison échoue.

Bonne nouvelle cependant: cette faille a été corrigée dans C++ 17! L’approche est toutefois un peu compliquée: en C++ 17, les variables membres statiques constexpr sont implicitement en ligne . Avoir inline appliqué aux variables est un nouveau concept en C++ 17, mais cela signifie en réalité qu’ils n’ont pas besoin d’une définition explicite n’importe où.

34
SethML

La solution la plus élégante n’est-elle pas en train de changer le char[] en:

static constexpr char * baz = "quz";

De cette façon, nous pouvons avoir la définition/déclaration/initialiseur dans 1 ligne de code.

5
deddebme

Ma solution de contournement pour le couplage externe de membres statiques consiste à utiliser constexpr getters de membres de référence (qui ne rencontre pas le problème @gnzlbg soulevé en tant que commentaire sur la réponse de @deddebme).
Cet idiome est important pour moi parce que je déteste avoir plusieurs fichiers .cpp dans mes projets et essaie de limiter le nombre à un, composé uniquement de #include S et d'une fonction main() une fonction.

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'
2
Josh Greifer

Sur mon environnement, la version de gcc est 5.4.0. L'ajout de "-O2" peut corriger cette erreur de compilation. Il semble que gcc puisse gérer ce cas en demandant une optimisation.

0
Haishan Zhou