Quelle est la différence entre constexpr
et const
?
Les deux mots-clés peuvent être utilisés dans la déclaration d'objets ainsi que de fonctions. La différence fondamentale appliquée aux objets est la suivante:
const
déclare un objet comme constant . Cela implique la garantie que, une fois initialisé, la valeur de cet objet ne changera pas et que le compilateur pourra en tirer parti pour des optimisations. Cela empêche également le programmeur d'écrire du code modifiant les objets qui ne devaient pas être modifiés après l'initialisation.
constexpr
déclare un objet apte à être utilisé dans ce que la norme appelle des expressions constantes . Mais notez que constexpr
n'est pas le seul moyen de le faire.
Appliquée aux fonctions , la différence fondamentale est la suivante:
const
ne peut être utilisé que pour des fonctions membres non statiques, pas pour des fonctions en général. Cela garantit que la fonction membre ne modifie aucun des membres de données non statiques.
constexpr
peut être utilisé avec des fonctions membres et non membres, ainsi que des constructeurs. Il déclare la fonction propre à être utilisée dans expressions constantes . Le compilateur ne l'acceptera que si la fonction répond à certains critères (7.1.5/3,4), surtout (†):
return
est autorisée. Dans le cas d'un constructeur, seules une liste d'initialisation, des typedefs et une assertion statique sont autorisées. (= default
et = delete
sont également autorisés.)asm
declaration, une instruction goto
, une instruction avec une étiquette autre que case
et default
, try-block, définition d'une variable de type non littéral, définition d'une variable de durée de stockage statique ou d'unités d'exécution, définition d'une variable pour laquelle aucune initialisation n'est effectuée.Comme indiqué ci-dessus, constexpr
déclare les deux objets ainsi que les fonctions aptes à être utilisés dans les expressions constantes. Une expression constante est plus que simplement constante:
Il peut être utilisé dans des endroits nécessitant une évaluation à la compilation, par exemple des paramètres de modèle et des spécificateurs de taille de tableau:
template<int N>
class fixed_size_list
{ /*...*/ };
fixed_size_list<X> mylist; // X must be an integer constant expression
int numbers[X]; // X must be an integer constant expression
Mais notez:
Déclarer quelque chose comme constexpr
ne garantit pas nécessairement qu'il sera évalué au moment de la compilation. Il peut être utilisé pour cela, mais il peut également être utilisé à d'autres emplacements évalués au moment de l'exécution.
Un objet peut être utilisable dans les expressions constantes sans que soit déclaré constexpr
. Exemple:
int main()
{
const int N = 3;
int numbers[N] = {1, 2, 3}; // N is constant expression
}
Cela est possible parce que N
, étant constant et initialisé au moment de la déclaration avec un littéral, satisfait aux critères d'une expression constante, même si elle n'est pas déclarée constexpr
.
Alors, quand dois-je utiliser constexpr
?
Un objet comme N
ci-dessus peut être utilisé comme expression constante sans que soit déclaré constexpr
. Ceci est vrai pour tous les objets qui sont:
const
[Ceci est dû à §5.19/2: une expression constante ne doit pas inclure une sous-expression qui implique "une modification de valeur d'une valeur à moins que […] une valeur de type intégral ou énumération [...]" Merci à Richard Smith d'avoir affirmation antérieure que cela était vrai pour tous les types littéraux.]
Pour qu'un fonction soit apte à être utilisé dans des expressions constantes, il doit être déclaré explicitement constexpr
; il ne suffit pas qu'il réponde aux critères des fonctions à expression constante. Exemple:
template<int N>
class list
{ };
constexpr int sqr1(int arg)
{ return arg * arg; }
int sqr2(int arg)
{ return arg * arg; }
int main()
{
const int X = 2;
list<sqr1(X)> mylist1; // OK: sqr1 is constexpr
list<sqr2(X)> mylist2; // wrong: sqr2 is not constexpr
}
Quand puis-je/devrais-je utiliser les deux, const
et constexpr
ensemble?
A. Dans les déclarations d'objet. Cela n'est jamais nécessaire lorsque les deux mots-clés font référence au même objet à déclarer. constexpr
implique const
.
constexpr const int N = 5;
est le même que
constexpr int N = 5;
Toutefois, notez qu'il peut arriver que les mots-clés renvoient à des parties différentes de la déclaration:
static constexpr int N = 3;
int main()
{
constexpr const int *NP = &N;
}
Ici, NP
est déclaré en tant qu’expression constante d’adresse, c’est-à-dire un pointeur qui est lui-même une expression constante. (Ceci est possible lorsque l'adresse est générée en appliquant l'opérateur d'adresse à une expression constante statique/globale.) Ici, constexpr
et const
sont obligatoires: constexpr
fait toujours référence à l'expression. étant déclaré (ici NP
), tandis que const
fait référence à int
(il déclare un pointeur sur const). Supprimer la variable const
rendrait l'expression illégale (car (a) un pointeur sur un objet non-const ne peut pas être une expression constante et (b) &N
est en fait un pointeur sur une constante ).
B. Dans les déclarations de fonctions membres. En C++ 11, constexpr
implique const
, alors qu'en C++ 14 et C++ 17, ce n'est pas le cas. Une fonction membre déclarée sous C++ 11 en tant que
constexpr void f();
doit être déclaré comme
constexpr void f() const;
sous C++ 14 pour rester utilisable en tant que fonction const
.
const
s'applique aux variables , et les empêche d'être modifiées dans votre code.
constexpr
indique au compilateur que cette expression donne une valeur constante de compilation , de sorte qu'il peut être utilisé à des endroits tels que les longueurs de tableaux, l'attribution à const
variables, etc. Le lien donné par Oli a beaucoup d'excellents exemples.
Fondamentalement, ce sont 2 concepts différents et peuvent (et devraient) être utilisés ensemble.
const
garantit qu’un programme ne modifie pas la valeur d’un objet . Cependant, const
ne garantit pas le type d'initialisation que l'objet subit.
Considérer:
const int mx = numeric_limits<int>::max(); // OK: runtime initialization
La fonction max()
renvoie simplement une valeur littérale. Cependant, l'initialiseur étant un appel de fonction, mx
est initialisé à l'exécution. Par conséquent, vous ne pouvez pas l'utiliser comme expression constante:
int arr[mx]; // error: “constant expression required”
constexpr
est un nouveau mot clé C++ 11 qui vous évite de créer des macros et des littéraux codés en dur. Cela garantit également, dans certaines conditions, que les objets subissent initialisation statique. Il contrôle le temps d'évaluation d'une expression. En imposant l'évaluation au moment de la compilation de son expression , constexpr
vous permet de définir true expressions constantes qui sont cruciales pour applications critiques, programmation système, modèles, et plus généralement, dans tout code reposant sur des constantes à la compilation.
Une fonction d'expression constante est une fonction déclarée constexpr
. Son corps doit être non virtuel et consister en une seule instruction return, à l'exception des expressions typedefs et static. Ses arguments et sa valeur de retour doivent avoir des types littéraux. Il peut être utilisé avec des arguments d'expression non constante, mais lorsque cela est fait, le résultat n'est pas une expression constante.
Une fonction d'expression constante est destinée à remplacer macros et littéraux codés en dur sans sacrifier les performances ou la sécurité du type.
constexpr int max() { return INT_MAX; } // OK
constexpr long long_max() { return 2147483647; } // OK
constexpr bool get_val()
{
bool res = false;
return res;
} // error: body is not just a return statement
constexpr int square(int x)
{ return x * x; } // OK: compile-time evaluation only if x is a constant expression
const int res = square(5); // OK: compile-time evaluation of square(5)
int y = getval();
int n = square(y); // OK: runtime evaluation of square(y)
Un objet expression constante est un objet déclaré constexpr
. Il doit être initialisé avec une expression constante ou une valeur rvalue construite par un constructeur d'expression constante avec des arguments d'expression constante.
Un objet d'expression constante se comporte comme s'il avait été déclaré const
, sauf qu'il nécessite une initialisation avant utilisation et que son initialiseur doit être une expression constante. Par conséquent, un objet expression constante peut toujours être utilisé dans le cadre d'une autre expression constante.
struct S
{
constexpr int two(); // constant-expression function
private:
static constexpr int sz; // constant-expression object
};
constexpr int S::sz = 256;
enum DataPacket
{
Small = S::two(), // error: S::two() called before it was defined
Big = 1024
};
constexpr int S::two() { return sz*2; }
constexpr S s;
int arr[s.two()]; // OK: s.two() called after its definition
Un constructeur d'expressions constantes est un constructeur déclaré constexpr
. Il peut avoir une liste d'initialisation de membres, mais son corps doit être vide, à l'exception des expressions de type et des assertions statiques. Ses arguments doivent avoir des types littéraux.
Un constructeur à expression constante permet au compilateur d’initialiser l’objet au moment de la compilation, à condition que les arguments du constructeur soient tous des expressions constantes.
struct complex
{
// constant-expression constructor
constexpr complex(double r, double i) : re(r), im(i) { } // OK: empty body
// constant-expression functions
constexpr double real() { return re; }
constexpr double imag() { return im; }
private:
double re;
double im;
};
constexpr complex COMP(0.0, 1.0); // creates a literal complex
double x = 1.0;
constexpr complex cx1(x, 0); // error: x is not a constant expression
const complex cx2(x, 1); // OK: runtime initialization
constexpr double xx = COMP.real(); // OK: compile-time initialization
constexpr double imaglval = COMP.imag(); // OK: compile-time initialization
complex cx3(2, 4.6); // OK: runtime initialization
Conseils du livre Effective Modern C++ par Scott Meyers à propos de constexpr
:
constexpr
les objets sont const et sont initialisés avec des valeurs connues lors de la compilation;constexpr
produisent des résultats au moment de la compilation lorsqu'elles sont appelées avec des arguments dont les valeurs sont connues lors de la compilation;constexpr
les objets et fonctions peuvent être utilisés dans un plus grand nombre de contextes que les objets et fonctions non -constexpr
;constexpr
fait partie de l'interface d'un objet ou d'une fonction.Source: tilisation de constexpr pour améliorer la sécurité, les performances et l’encapsulation en C++ .
D'après le livre du "Langage de programmation C++ 4ème édition" de Bjarne Stroustrup
• const: signifiant en gros "Je promets de ne pas modifier cette valeur" (§7.5). Ceci est principalement utilisé pour spécifier les interfaces, de sorte que les données puissent être transmises aux fonctions sans crainte d'être modifiées.
Le compilateur applique la promesse faite par const.
• constexpr: signifiant approximativement ‘’ à évaluer au moment de la compilation ’’ (§10.4). Ceci est utilisé principalement pour spécifier des constantes, pour permettre
Par exemple:
const int dmv = 17; // dmv is a named constant
int var = 17; // var is not a constant
constexpr double max1 = 1.4*square(dmv); // OK if square(17) is a constant expression
constexpr double max2 = 1.4∗square(var); // error : var is not a constant expression
const double max3 = 1.4∗square(var); //OK, may be evaluated at run time
double sum(const vector<double>&); // sum will not modify its argument (§2.2.5)
vector<double> v {1.2, 3.4, 4.5}; // v is not a constant
const double s1 = sum(v); // OK: evaluated at run time
constexpr double s2 = sum(v); // error : sum(v) not constant expression
Pour qu'une fonction soit utilisable dans une expression constante, c'est-à-dire dans une expression qui sera évaluée par le compilateur, elle doit être définie constexpr.
Par exemple:
constexpr double square(double x) { return x∗x; }
Pour être constexpr, une fonction doit être assez simple: il suffit d’une déclaration de retour calculant une valeur. Une fonction constexpr peut être utilisée pour des arguments non constants, mais lorsque cela est fait, le résultat n'est pas une expression constante. Nous permettons à une fonction constexpr d’être appelée avec des arguments non-constant-expression dans des contextes ne nécessitant pas d’expressions constantes, de sorte que nous n’ayons pas à définir essentiellement la même fonction deux fois: une fois pour les expressions constantes et une fois pour les variables.
À quelques endroits près, des expressions constantes sont requises par les règles de langage (par exemple, les limites d'un tableau (§2.2.5, §7.3), les étiquettes de casse (§2.2.4, §9.4.2), certains arguments de modèle ( §25.2), et les constantes déclarées en utilisant constexpr). Dans d'autres cas, l'évaluation à la compilation est importante pour la performance. Indépendamment des problèmes de performance, la notion d'immutabilité (d'un objet avec un état immuable) est une préoccupation de conception importante (§10.4).
const
et constexpr
peuvent être appliqués aux variables et aux fonctions. Même s'ils se ressemblent, ce sont des concepts très différents.
const
et constexpr
signifient que leurs valeurs ne peuvent plus être modifiées après leur initialisation. Donc par exemple:
const int x1=10;
constexpr int x2=10;
x1=20; // ERROR. Variable 'x1' can't be changed.
x2=20; // ERROR. Variable 'x2' can't be changed.
La principale différence entre const
et constexpr
est le moment où leurs valeurs d'initialisation sont connues (évaluées). Alors que les valeurs de const
variables peuvent être évaluées à la fois lors de la compilation et de l'exécution, constexpr
sont toujours évaluées lors de la compilation. Par exemple:
int temp=Rand(); // temp is generated by the the random generator at runtime.
const int x1=10; // OK - known at compile time.
const int x2=temp; // OK - known only at runtime.
constexpr int x3=10; // OK - known at compile time.
constexpr int x4=temp; // ERROR. Compiler can't figure out the value of 'temp' variable at compile time so `constexpr` can't be applied here.
Le principal avantage de savoir si la valeur est connue au moment de la compilation ou de l'exécution est le fait que les constantes de compilation peuvent être utilisées chaque fois que des constantes de compilation sont nécessaires. Par exemple, C++ ne vous permet pas de spécifier des tableaux de type C avec des longueurs variables.
int temp=Rand(); // temp is generated by the the random generator at runtime.
int array1[10]; // OK.
int array2[temp]; // ERROR.
Cela signifie donc que:
const int size1=10; // OK - value known at compile time.
const int size2=temp; // OK - value known only at runtime.
constexpr int size3=10; // OK - value known at compile time.
int array3[size1]; // OK - size is known at compile time.
int array4[size2]; // ERROR - size is known only at runtime time.
int array5[size3]; // OK - size is known at compile time.
Donc, les variables const
peuvent définir à la fois compiler les constantes de temps comme size1
qui peut être utilisé pour spécifier les tailles de tableau et constantes d'exécution comme size2
qui sont connus uniquement à l'exécution et ne peuvent pas être utilisés pour définir la taille des tableaux. Par contre, constexpr
définit toujours des constantes de compilation permettant de spécifier la taille des tableaux.
const
et constexpr
peuvent également être appliqués à des fonctions. Une fonction const
doit être une fonction membre (méthode, opérateur) où application du mot clé const
signifie que la méthode ne peut pas modifier les valeurs de leurs champs membres (non statiques). Par exemple.
class test
{
int x;
void function1()
{
x=100; // OK.
}
void function2() const
{
x=100; // ERROR. The const methods can't change the values of object fields.
}
};
Un constexpr
est un concept différent. Il marque une fonction (membre ou non-membre) comme fonction pouvant être évaluée lors de la compilation si les constantes de compilation sont passées comme arguments. Par exemple, vous pouvez écrire ceci.
constexpr int func_constexpr(int X, int Y)
{
return(X*Y);
}
int func(int X, int Y)
{
return(X*Y);
}
int array1[func_constexpr(10,20)]; // OK - func_constexpr() can be evaluated at compile time.
int array2[func(10,20)]; // ERROR - func() is not a constexpr function.
int array3[func_constexpr(10,Rand())]; // ERROR - even though func_constexpr() is the 'constexpr' function, the expression 'constexpr(10,Rand())' can't be evaluated at compile time.
Au fait, les fonctions constexpr
sont les fonctions C++ standard pouvant être appelées même si des arguments non constants sont passés. Mais dans ce cas, vous obtenez les valeurs non constexpr.
int value1=func_constexpr(10,Rand()); // OK. value1 is non-constexpr value that is evaluated in runtime.
constexpr int value2=func_constexpr(10,Rand()); // ERROR. value2 is constexpr and the expression func_constexpr(10,Rand()) can't be evaluated at compile time.
La constexpr
peut également être appliquée aux fonctions membres (méthodes), aux opérateurs et même aux constructeurs. Par exemple.
class test2
{
static constexpr int function(int value)
{
return(value+1);
}
void f()
{
int x[function(10)];
}
};
Un échantillon plus "fou".
class test3
{
public:
int value;
// constexpr const method - can't chanage the values of object fields and can be evaluated at compile time.
constexpr int getvalue() const
{
return(value);
}
constexpr test3(int Value)
: value(Value)
{
}
};
constexpr test3 x(100); // OK. Constructor is constexpr.
int array[x.getvalue()]; // OK. x.getvalue() is constexpr and can be evaluated at compile time.
Comme @ 0x499602d2 l'a déjà souligné, const
garantit uniquement qu'une valeur ne peut pas être modifiée après l'initialisation, alors que constexpr
(introduit en C++ 11) garantit que la variable est une constante de temps de compilation.
Considérez l'exemple suivant (tiré de LearnCpp.com):
cout << "Enter your age: ";
int age;
cin >> age;
const int myAge{age}; // works
constexpr int someAge{age}; // error: age can only be resolved at runtime
Un const int var
peut être défini de manière dynamique sur une valeur au moment de l'exécution et une fois défini sur cette valeur, il ne peut plus être modifié.
Un constexpr int var
ne peut pas être défini dynamiquement au moment de l'exécution, mais au moment de la compilation. Et une fois défini sur cette valeur, il ne peut plus être modifié.
Voici un exemple solide:
int main(int argc, char*argv[]) {
const int p = argc;
// p = 69; // cannot change p because it is a const
// constexpr int q = argc; // cannot be, bcoz argc cannot be computed at compile time
constexpr int r = 2^3; // this works!
// r = 42; // same as const too, it cannot be changed
}
L'extrait ci-dessus compile bien et j'ai commenté ceux qui causent des erreurs.
Tout d'abord, les deux sont des qualificatifs en c ++. Une variable déclarée const doit être initialisée et ne pourra plus être modifiée. Donc, généralement, une variable déclarée en tant que const aura une valeur même avant la compilation.
Mais, pour constexpr, c'est un peu différent.
Pour constexpr, vous pouvez donner une expression pouvant être évaluée lors de la compilation du programme.
Évidemment, la variable déclarée comme constexper ne peut pas être modifiée à l'avenir, tout comme const.