Voici mon code:
class test{
public:
constexpr test(){
}
constexpr int operator+(const test& rhs){
return 1;
}
};
int main(){
test t; //constexpr Word isn't necessary
constexpr int b = t+test(); // works at compile time!
int w = 10; // ERROR constexpr required
constexpr int c = w + 2; // Requires w to be constexpr
return 0;
}
Je remarque que cela a fonctionné même si je n'ai pas spécifié que test devait être constexpr
. J'ai essayé de répliquer le résultat en faisant de même avec int
mais j'obtiens des erreurs. Plus précisément, il veut que mon int w
À l'intérieur du constexpr int c = w + 2;
Soit constexpr
. De ma première tentative qui utilise test
, Cela a-t-il fonctionné parce que j'ai déjà utilisé constexpr
sur le constructeur? Si tel est le cas, serait-il bon de supposer que toutes les classes qui ont constexpr
sur leurs constructeurs auront pour résultat que tous les objets instanciés ou créés avec lui seront constexpr
?
Question bonus:
Si j'ai un constructeur constexpr
, est-ce mauvais de faire quelque chose comme ça? test * t = new test();
?
Avoir un constructeur constexpr ne fait pas de déclarations de cette variable automatiquement constexpr, donc t
n'est pas un constexpr. Ce qui se passe dans ce cas, c'est que vous appelez une fonction constexpr, cette ligne:
constexpr int b = t+test();
peut être visualisé comme suit:
constexpr int b = t.operator+( test() );
La question est donc de savoir si test()
est une expression constante, ce qu'elle est puisque le constructeur est constexpr et ne tombe sous aucune des exceptions du projet de section standard C++ 11 5.19
[expr.const] paragraphe 2
qui dit:
Une expression conditionnelle est une expression constante de base, sauf si elle implique l'un des éléments suivants comme une sous-expression potentiellement évaluée [...]
et comprend la puce suivante:
- une invocation d'une fonction autre qu'un constructeur constexpr pour une classe littérale ou une fonction constexpr [Remarque: la résolution de surcharge (13.3) est appliquée comme d'habitude - note de fin];
[...]
une invocation d'un constructeur constexpr avec des arguments qui, lorsqu'ils sont substitués par une substitution d'invocation de fonction (7.1.5), ne produisent pas toutes les expressions constantes pour les appels de constructeur et les expressions complètes dans les initialiseurs mem
une invocation d'une fonction constexpr ou d'un constructeur constexpr qui dépasserait les limites de récursivité définies par l'implémentation (voir l'annexe B);
Nous pouvons le voir plus facilement en apportant quelques petites modifications à test
en introduisant une variable membre x
:
class test{
public:
constexpr test(){
}
constexpr int operator+(const test& rhs) const {
return x + 1 ;
}
int x = 10 ;
};
Tenter d'y accéder dans operator +
Et nous pouvons voir que la ligne suivante échoue maintenant:
constexpr int b = t+test();
avec l'erreur suivante de clang (voir en direct):
error: constexpr variable 'b' must be initialized by a constant expression
constexpr int b = t+test(); // works at compile time!
^ ~~~~~~~~
note: read of non-constexpr variable 't' is not allowed in a constant expression
return x + 1 ;
^
Il échoue car t
n'est pas une variable constexpr et donc ses sous-objets ne sont pas non plus des variables constexpr.
Votre deuxième exemple:
constexpr int c = w + 2;
ne fonctionne pas car il relève de l'une des exceptions du projet de section standard C++ 11 5.19
[expr.const] :
une conversion lvalue-to-rvalue (4.1) sauf si elle est appliquée à
[...]
- une valeur gl de type intégral ou énumération qui fait référence à un objet const non volatile avec une initialisation précédente, initialisé avec une expression constante, ou
L'effet qu'un constructeur constexpr
a sur le type de classe peut être lu dans la norme C++
3.9 Types
(...)
Un type est un type littéral s'il est:
- c'est un type d'agrégat (8.5.1) ou a au moins un constructeur constexpr ou un modèle de constructeur qui n'est pas un constructeur de copie ou de déplacement
(...)
Ainsi, les constructeurs constexpr
signifient que l'initialisation statique peut être effectuée et utilise comme this un sont possibles:
#include <iostream>
struct test {
int val;
constexpr test(int val) : val(val) { }
};
template<int N>
struct CC {
double m[N];
};
int main()
{
CC<test(6).val> k; // usage where compile time constant is required
std::cout << std::end(k.m) - std::begin(k.m) << std::endl;
return 0;
}
Le simple fait que test
soit une classe littérale ne signifie pas que toutes ses instances seront des expressions constantes :
#include <iostream>
struct test {
int val;
constexpr test(int val) : val(val) { }
};
int main()
{
test a(1);
++a.val;
std::cout << a.val << std::endl;
return 0;
}
Dans l'exemple ci-dessus, l'instance a
n'a pas été déclarée comme constante, même si a
pourrait être une constante constexpr
, elle n'en est pas une (elle peut donc être modifiée).
La clé constexpr Word de mes expériences dans cette réponse indique plus ou moins au compilateur qu'il doit être capable de résoudre statiquement tous les chemins de code donnés dans cet appel. Autrement dit, au moins en ce moment (il semblerait), tout doit être déclaré constexpr le long de ce chemin de code sinon il échouera. Par exemple, dans votre code, l'affectation initiale de constexpr à b échouera si vous ne déclarez pas l'opérateur ou le constructeur constexpr. Il semble que le constexpr ne prend effet que lorsque vous attribuez à une variable qui est déclarée constexpr, sinon il ne semble que servir de conseiller au compilateur pour que le chemin de code puisse être optimisé via une évaluation statique, mais il n'est pas garanti de le faire si vous ne l'instruisez pas explicitement avec une affectation de variable constexpr.
Cela étant dit, il semblerait que la déclaration d'un constructeur constexpr n'a aucun effet dans des circonstances normales. Le code machine ci-dessous a été produit avec la ligne de commande suivante:
g++ -std=c++11 -Wall -g -c main.cpp -o obj/Debug/main.o
g++ -o bin/Debug/TestProject obj/Debug/main.o
Et donc votre affectation b produit ce code:
0x4005bd Push rbp
0x4005be mov rbp,rsp
0x4005c1 mov DWORD PTR [rbp-0x4],0x1
0x4005c8 mov eax,0x0
0x4005cd pop rbp
0x4005ce ret
Cependant, si vous supprimez la déclaration constexpr sur la variable b:
0x4005bd Push rbp
0x4005be mov rbp,rsp
0x4005c1 sub rsp,0x10
0x4005c5 lea rax,[rbp-0x5]
0x4005c9 mov rdi,rax
0x4005cc call 0x4005ee <test::test()>
0x4005d1 lea rdx,[rbp-0x5]
0x4005d5 lea rax,[rbp-0x6]
0x4005d9 mov rsi,rdx
0x4005dc mov rdi,rax
0x4005df call 0x4005f8 <test::operator+(test const&) const>
0x4005e4 mov DWORD PTR [rbp-0x4],eax
0x4005e7 mov eax,0x0
0x4005ec leave
0x4005ed ret
Il semble être géré comme si l'opérateur et le constructeur n'étaient pas déclarés constexpr, mais c'est une situation où vous devriez vraiment consulter les détails de votre compilateur.