web-dev-qa-db-fra.com

Pourquoi les membres de données non statiques ne peuvent-ils pas être constexpr?

Ceci est un code valide:

struct S {
  constexpr S(int x, int y): xVal(x), yVal(y) {}
  constexpr S(int x): xVal(x) {}
  constexpr S() {}

  const int xVal { 0 };
  const int yVal { 0 };
};

Mais ici, je voudrais vraiment déclarer xVal et yValconstexpr-- comme ceci:

struct S {
  constexpr S(int x, int y): xVal(x), yVal(y) {}
  constexpr S(int x): xVal(x) {}
  constexpr S() {}

  constexpr int xVal { 0 };         // error!
  constexpr int yVal { 0 };         // error!
};

Comme indiqué, le code ne se compilera pas. La raison en est que (selon 7.1.5/1), seuls les membres de données statiques peuvent être déclarés constexpr. Mais pourquoi?

27
KnowItAllWannabe

Réfléchissez à ce que signifie constexpr. Cela signifie que je peux résoudre cette valeur au moment de la compilation.

Ainsi, une variable membre d'une classe ne peut pas être elle-même un constexpr ... l'instance à laquelle xVal appartient n'existe pas jusqu'au moment de l'instanciation! La propriété de xVal pourrait être constexp, ce qui ferait de xVal un constexpr, mais xVal ne pourrait jamais être constexpr seul.

Cela ne signifie pas que ces valeurs ne peuvent pas être une expression const ... en fait, une instance constexpr de la classe peut utiliser les variables comme expressions const:

struct S {
  constexpr S(int x, int y): xVal(x), yVal(y) {}
  constexpr S(int x): xVal(x) {}
  constexpr S() {}

  int xVal { 0 };
  int yVal { 0 };
};

constexpr S s;

template <int f>//requires a constexpr
int foo() {return f;}

int main()
{
   cout << "Hello World" << foo<s.xVal>( )<< endl; 

   return 0;
}

Edit: Donc, il y a eu beaucoup de discussions ci-dessous qui ont examiné qu'il y avait quelques questions implicites ici.

"pourquoi ne puis-je pas forcer toutes les instances d'une classe à être constexpr en déclarant ses membres constexpr?"

Prenons l'exemple suivant:

//a.h
struct S;
struct A {std::unique_ptr<S> x; void Foo(); A();/*assume A() tries to instantiate an x*/}

//main.cpp

int main(int argc, char** argv) {
  A a;
  a->foo();
}


//S.h
struct S {
  constexpr S(int x, int y): xVal(x), yVal(y) {}
  constexpr S(int x): xVal(x) {}
  constexpr S() {}

  constexpr int xVal { 0 };         // error!
  constexpr int yVal { 0 };
};

La définition de A et S pourrait être dans des unités de compilation complètement différentes, donc le fait que S doit être constexpr peut ne pas être connu avant le temps de liaison, surtout si l'implémentation de A est oubliée. De tels cas ambigus seraient difficiles à déboguer et difficiles à mettre en œuvre. Ce qui est pire, c'est que l'interface pour S pourrait être exposée entièrement dans une bibliothèque partagée, interface COM, ect ... Cela pourrait changer complètement toutes les infrastructures d'une bibliothèque partagée et cela serait probablement inacceptable.

Une autre raison serait à quel point c'est contagieux. Si l'un des membres d'une classe était constexpr, tous les membres (et tous leurs membres) et toutes les instances devraient être constexpr. Prenez le scénario suivant:

//S.h
struct S {
  constexpr S(int x, int y): xVal(x), yVal(y) {}
  constexpr S(int x): xVal(x) {}
  constexpr S() {}

  constexpr int xVal { 0 };         // error!
  int yVal { 0 };
};

Toute instance de S devrait être constexpr pour pouvoir contenir exclusivement un constexprxval. yVal devient intrinsèquement constexpr car xVal l'est. Il n'y a pas de raison technique pour le compilateur que vous ne puissiez pas faire cela (je ne pense pas) mais cela ne semble pas très similaire à C++.

"D'accord, mais je veux VRAIMENT faire toutes les instances d'une classe constexpr. Quelle est la limitation technique qui m'empêche de le faire".

Rien d'autre que le comité des normes ne pensait probablement que c'était une bonne idée. Personnellement, je trouve qu'il a très peu d'utilité ... Je ne veux pas vraiment définir comment les gens utilisent ma classe, juste définir comment ma classe se comporte quand ils l'utilisent. Lorsqu'ils l'utilisent, ils peuvent déclarer des instances spécifiques comme constexpr (comme ci-dessus). Si j'ai un bloc de code sur lequel je voudrais une instance de constexpr, je le ferais avec un modèle:

template <S s>
function int bar(){return s.xVal;}

int main()
{
   cout << "Hello World" << foo<bar<s>()>( )<< endl; 

   return 0;
}

Bien que je pense que vous seriez mieux avec une fonction constexpr qui pourrait être utilisée à la fois de manière restrictive et non restrictive?

constexpr int bar(S s) { return s.xVal; }
22
IdeaHat