Différence entre `constexpr` et` const`

Quelle est la différence entre constexpr et const ?

  • Quand puis-je en utiliser un seul?
  • Quand puis-je utiliser les deux et comment en choisir un?

Signification et syntaxe de base

Les deux mots-clés peuvent être utilisés dans la déclaration d’objects ainsi que dans les fonctions. La différence fondamentale appliquée aux objects est la suivante:

  • const déclare un object constant . Cela implique une garantie que, une fois initialisé, la valeur de cet object ne changera pas, et le compilateur peut utiliser ce fait pour les optimisations. Cela permet également d’empêcher le programmeur d’écrire du code qui modifie des objects qui n’étaient pas destinés à être modifiés après l’initialisation.

  • constexpr déclare un object adapté à l’utilisation de ce que le standard appelle des expressions constantes . Mais notez que constexpr n’est pas la seule façon de faire cela.

Appliqué aux fonctions, la différence fondamentale est la suivante:

  • const ne peut être utilisé que pour des fonctions membres non statiques, pas des fonctions en général. Cela donne une garantie que la fonction membre ne modifie aucun des membres de données non statiques.

  • constexpr peut être utilisé avec les fonctions membres et non membres, ainsi qu’avec les constructeurs. Il déclare la fonction adaptée pour une utilisation dans des expressions constantes . Le compilateur ne l’acceptera que si la fonction répond à certains critères (7.1.5 / 3,4), surtout (†) :

    • Le corps de la fonction doit être non virtuel et extrêmement simple: à part les typedefs et les assertions statiques, une seule déclaration de return est autorisée. Dans le cas d’un constructeur, seule une liste d’initialisation, des typedefs et des assertions statiques sont autorisés. ( = default et = delete sont autorisés aussi, cependant.)
    • A partir de C ++ 14, les règles sont plus assouplies, ce qui est autorisé depuis lors dans une fonction constexpr: déclaration asm, instruction goto, instruction avec une étiquette autre que case et default, try-block, définition d’une variable de non -literal type, définition d’une variable de durée de stockage statique ou thread, définition d’une variable pour laquelle aucune initialisation n’est effectuée.
    • Les arguments et le type de retour doivent être des types littéraux (c’est-à-dire, en général, des types très simples, généralement des scalaires ou des agrégats)

Expressions constantes

Comme indiqué ci-dessus, constexpr déclare les deux objects ainsi que les fonctions aptes à être utilisées dans les expressions constantes. Une expression constante est plus que constante:

  • Il peut être utilisé dans des endroits nécessitant une évaluation au moment de la compilation, par exemple, des parameters de modèle et des spécificateurs de taille de tableau:

     template class fixed_size_list { /*...*/ }; fixed_size_list mylist; // X must be an integer constant expression int numbers[X]; // X must be an integer constant expression 
  • Mais remarquez:

    • 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é dans d’autres endroits évalués au moment de l’exécution.

    • Un object peut être utilisable dans des expressions constantes sans être déclaré constexpr . Exemple:

       int main() { const int N = 3; int numbers[N] = {1, 2, 3}; // N is constant expression return 0; } 

    Ceci 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 s’il n’est pas déclaré constexpr .

Alors, quand dois-je réellement utiliser constexpr ?

  • Un object comme N ci-dessus peut être utilisé comme expression constante sans être déclaré constexpr . Cela est vrai pour tous les objects qui sont:

    • const
    • de type intégral ou de dénombrement et
    • initialisé au moment de la déclaration avec une expression qui est elle-même une expression constante

    [Ceci est dû au § 5.19 / 2: une expression constante ne doit pas inclure de sous-expressions qui impliquent “une modification de lvalue-à-valeur à moins que […] une valeur de type intégrale ou énumération […]” Merci à Richard Smith affirmation antérieure que cela était vrai pour tous les types littéraux.]

  • Pour qu’une fonction puisse être utilisée dans des expressions constantes, elle doit être explicitement déclarée constexpr ; il ne suffit pas qu’il satisfasse simplement aux critères des fonctions d’expression constante. Exemple:

     template class list { }; constexpr int sqr1(int arg) { return arg * arg; } int sqr2(int arg) { return arg * arg; } int main() { const int X = 2; list mylist1; // OK: sqr1 is constexpr list mylist2; // wrong: sqr2 is not constexpr return 0; } 

Quand puis-je / devrais-je utiliser les deux, const et constexpr ensemble?

A. Dans les déclarations d’object. Cela n’est jamais nécessaire lorsque les deux mots-clés font référence au même object à déclarer. constexpr implique const .

 constexpr const int N = 5; 

est le même que

 constexpr int N = 5; 

Cependant, notez qu’il peut y avoir des situations où les mots-clés se réfèrent chacun à différentes parties de la déclaration:

 static constexpr int N = 3; int main() { constexpr const int *NP = &N; return 0; } 

Ici, NP est déclarée en tant qu’expression constante d’adresse, c’est-à-dire en tant que 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 de constante statique / globale.) Ici, constexpr et const sont requirejs: constexpr fait toujours référence à l’expression déclarée (ici NP ), tandis que const fait référence à int (il déclare un pointeur sur const). Supprimer le const rendrait l’expression illégale (car (a) un pointeur sur un object non-const ne peut pas être une expression constante et (b) &N est en fait un pointeur sur constante).

B. Dans les déclarations de fonction membres. En C ++ 11, constexpr implique également la const pour les fonctions membres. Cependant, cela risque de changer en C ++ 14. Selon les brouillons actuels, constexpr impliquera const seulement pour les objects , pas pour les fonctions membres, en raison d’une modification proposée au § 7.1.5 / 8. Par conséquent, une fonction membre déclarée sous C ++ 11 comme

 constexpr void f(); 

devra être déclaré comme

 constexpr void f() const; 

sous C ++ 14 afin d’être encore utilisable en tant que fonction const . Il est constexpr marquer vos fonctions membres constexpr comme const maintenant pour éviter de devoir modifier beaucoup de code ultérieurement.


(†) Les conditions pour les fonctions constexpr acceptables seront probablement assouplies pour C ++ 14. Une proposition de Richard Smith a récemment été adoptée dans le projet C ++ 14 .

const s’applique aux variables et empêche leur modification dans votre code.

constexpr indique au compilateur que cette expression génère une constante de temps de compilation , de sorte qu’il peut être utilisé dans des endroits tels que les longueurs de tableaux, l’affectation de variables const , etc. Le lien fourni par Oli contient de nombreux exemples excellents.

Fondamentalement, ils constituent 2 concepts différents et peuvent (et devraient) être utilisés ensemble.

Vue d’ensemble

  • const garantit qu’un programme ne modifie pas la valeur d’un object . Cependant, const ne garantit pas le type d’initialisation de l’object.

    Considérer:

     const int mx = numeric_limits::max(); // OK: runtime initialization 

    La fonction max() renvoie simplement une valeur littérale. Cependant, l’initialiseur étant un appel de fonction, mx subit une initialisation à l’exécution. Par conséquent, vous ne pouvez pas l’utiliser comme une 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. Il garantit également, sous certaines conditions, que les objects subissent une initialisation statique . Il contrôle le temps d’évaluation d’une expression. En imposant une évaluation à la compilation de son expression , constexpr vous permet de définir de véritables expressions constantes essentielles pour les applications à constexpr critique, la programmation système, les modèles et, de manière générale, tout code reposant sur des constantes de compilation.

Fonctions d’expression constante

Une fonction d’expression constante est une fonction déclarée constexpr . Son corps doit être non virtuel et se composer d’une seule déclaration de retour, à l’exception des typedefs et des assertions statiques. 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 les macros et les littéraux codés en dur sans sacrifier les performances ou la sécurité de 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: comstack-time evaluation only if x is a constant expression const int res = square(5); // OK: comstack-time evaluation of square(5) int y = getval(); int n = square(y); // OK: runtime evaluation of square(y) 

Objets d’expression constante

Un object d’expression constante est un object déclaré constexpr . Il doit être initialisé avec une expression constante ou une rvalue construite par un constructeur d’expression constante avec des arguments d’expression constante.

Un object d’expression constante se comporte comme s’il était 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 object d’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 

Constructeurs à expression constante

Un constructeur à expression constante est un constructeur déclaré constexpr . Il peut avoir une liste d’initialisation des membres mais son corps doit être vide, à l’exception des typedefs et des assertions statiques. Ses arguments doivent avoir des types littéraux.

Un constructeur à expression constante permet au compilateur d’initialiser l’object à 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: comstack-time initialization constexpr double imaglval = COMP.imag(); // OK: comstack-time initialization complex cx3(2, 4.6); // OK: runtime initialization 

Trucs du livre Effective Modern C ++ de Scott Meyers sur constexpr :

  • constexpr objects constexpr sont const et sont initialisés avec des valeurs connues lors de la compilation;
  • constexpr fonctions constexpr produisent des résultats à la compilation lorsqu’elles sont appelées avec des arguments dont les valeurs sont connues lors de la compilation;
  • constexpr objects et fonctions constexpr peuvent être utilisés dans un plus large éventail de contextes que les objects et fonctions non constexpr ;
  • constexpr fait partie de l’interface d’un object ou d’une fonction.

Source: Utilisation de constexpr pour améliorer la sécurité, les performances et l’encapsulation en C ++ .

Selon le livre de “The C ++ Programming Language 4th Editon” par Bjarne Stroustrup
const : signifiant grosso modo «je promets de ne pas changer cette valeur» (§7.5). Ceci est principalement utilisé pour spécifier des interfaces, afin 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 grosso modo «être évalué 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&); // sum will not modify its argument (§2.2.5) vector 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 sur constexpr .
Par exemple:

 constexpr double square(double x) { return x∗x; } 

Pour être constexpr, une fonction doit être assez simple: juste 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 d’appeler une fonction constexpr avec des arguments d’expression non constante dans des contextes ne nécessitant pas d’expressions constantes, de sorte que nous ne devons pas définir essentiellement la même fonction deux fois: une fois pour les expressions constantes et une fois pour les variables.
Dans quelques endroits, les règles de langage exigent des expressions constantes (par exemple, limites de tableau (§2.2.5, §7.3), étiquettes de cas (§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 au moment de la compilation est importante pour la performance. Indépendamment des problèmes de performance, la notion d’immuabilité (d’un object dont l’état est immuable) constitue un problème de conception important (§10.4).

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 dans C ++ 11) garantit que la variable est une constante de compilation.
Prenons l’exemple suivant (à partir 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 

constexpr et constexpr peuvent être appliqués aux variables et aux fonctions. Même si elles sont similaires, ce sont en fait des concepts très différents.

constexpr et constexpr signifient que leurs valeurs ne peuvent pas ê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 des variables const peuvent être évaluées à la fois à la compilation et à l’exécution, constexpr est toujours évalué à l’exécution. Par exemple:

 int temp=rand(); // temp is generated by the the random generator at runtime. const int x1=10; // OK - known at comstack time. const int x2=temp; // OK - known only at runtime. constexpr int x3=10; // OK - known at comstack time. constexpr int x4=temp; // ERROR. Comstackr can't figure out the value of 'temp' variable at comstack 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 des constantes de temps 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 les tableaux C avec les longueurs variables.

 int temp=rand(); // temp is generated by the the random generator at runtime. int array1[10]; // OK. int array2[temp]; // ERROR. 

Donc, cela signifie que:

 const int size1=10; // OK - value known at comstack time. const int size2=temp; // OK - value known only at runtime. constexpr int size3=10; // OK - value known at comstack time. int array3[size1]; // OK - size is known at comstack time. int array4[size2]; // ERROR - size is known only at runtime time. int array5[size3]; // OK - size is known at comstack time. 

Les variables const peuvent donc définir des constantes de temps de compilation telles que size1 qui peuvent être utilisées pour spécifier des tailles de tableau et des constantes d’exécution telles que size2 , connues uniquement à l’exécution et ne pouvant pas être utilisées pour définir des tailles de tableau. En revanche, constexpr définit toujours des constantes de temps de compilation pouvant spécifier des tailles de tableau.

constexpr et constexpr peuvent également être appliqués aux fonctions. Une fonction const doit être une fonction membre (méthode, opérateur) où l’application du mot clé const signifie que la méthode ne peut pas modifier les valeurs de ses 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 à l’exécution si les constantes de compilation sont passées en argument . 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 comstack 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 comstack time. 

Au fait, les fonctions constexpr sont les fonctions C ++ habituelles qui peuvent être appelées même si des arguments non constants sont transmis. 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 comstack time. 

Le constexpr peut également être appliqué 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 comstack 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 comstack time. 

Une const int var peut être définie dynamicment sur une valeur à l’exécution et une fois définie sur cette valeur, elle ne peut plus être modifiée.

Un constexpr int var ne peut pas être défini dynamicment à l’exécution, mais plutôt à la compilation. Et une fois que cette valeur est définie, elle ne peut plus être modifiée.

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 comstack time constexpr int r = 2^3; // this works! // r = 42; // same as const too, it cannot be changed } 

L’extrait de code ci-dessus comstack correctement et j’ai commenté ceux qui causent une erreur.