web-dev-qa-db-fra.com

Erreur de référence non définie pour un membre constexpr statique

Considérez ce code:

#include <vector>

struct A {
  static constexpr int kDefaultValue = -1;
  std::vector<int> v;
  A(int n): v(n, A::kDefaultValue) {}
};

int main() {
  A(10);
  return 0;
}

Il ne parvient pas à se lier (llvm clang, gcc 4.9, tous deux sous OS X):

Undefined symbols for architecture x86_64:
  "A::kDefaultValue", referenced from:
      A::(int) in main.cpp.o
ld: symbol(s) not found for architecture x86_64

La question est: qu'est-ce qui ne va pas? Il peut être corrigé par static_cast- ing A::kDefaultValue à int. Ou en déplaçant kDefaultValue hors de A. Les deux cas semblent laids. Est-ce une autre façon de faire le lien?

21
y0prst

Ce comportement me contrarie maintes et maintes fois. La cause du problème est que votre

A(int n): v(n, A::kDefaultValue) {}

odr-uses le membre static constexpr, car le constructeur de v prend une constante référence deuxième argument. L'utilisation odr nécessite une définition quelque part, c'est-à-dire.

const int A::kDefaultValue;

dans une unité de compilation (qui est compilée et liée à main()). Cette exigence a été supprimée en C++ 17 et la définition correspondante (comme ci-dessus) est déconseillée.

Cependant, une définition n'est pas toujours possible (par exemple pour les membres des modèles de classe) et le moyen le plus simple d'éviter à la fois la définition et votre erreur est

A(int n): v(n, int(A::kDefaultValue)) {}

ce qui crée un temporaire à passer au constructeur de v (mais comme ce dernier est entièrement en ligne, le compilateur peut optimiser cela).

19
Walter

Le comportement a changé depuis C++ 17. Avant C++ 17, même un membre de données statique constexpr doit être initialisé dans la définition de classe, la définition à la portée de l'espace de noms est toujours requise; Depuis C++ 17, la définition de la portée de l'espace de noms n'est plus requise.

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. (depuis C++ 17)

Compiler votre code avec un compilateur prenant en charge C++ 17 fonctionnerait bien.

Démo LIVE avec gcc7

11
songyuanyao