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 yVal
constexpr
-- 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?
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.
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 constexpr
xval
. 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++.
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; }