Longueur de calcul d’une chaîne C au moment de la compilation. Est-ce vraiment une constexpr?

J’essaie de calculer la longueur d’une chaîne littérale au moment de la compilation. Pour ce faire, j’utilise le code suivant:

#include  int constexpr length(const char* str) { return *str ? 1 + length(str + 1) : 0; } int main() { printf("%d %d", length("abcd"), length("abcdefgh")); } 

Tout fonctionne comme prévu, le programme imprime 4 et 8. Le code assembleur généré par clang montre que les résultats sont calculés au moment de la compilation:

 0x100000f5e: leaq 0x35(%rip), %rdi ; "%d %d" 0x100000f65: movl $0x4, %esi 0x100000f6a: movl $0x8, %edx 0x100000f6f: xorl %eax, %eax 0x100000f71: callq 0x100000f7a ; symbol stub for: printf 

Ma question: est-il garanti par la norme que la fonction de length sera évaluée au moment de la compilation?

Si cela est vrai la porte pour la compilation des calculs de chaînes de temps vient de s’ouvrir pour moi … par exemple je peux calculer des hachages au moment de la compilation et beaucoup plus …

Les expressions constantes ne sont pas garanties d’être évaluées au moment de la compilation, nous n’avons qu’une citation non normative tirée du projet de section 5.19 norme C ++ Expressions constantes qui dit ceci:

[…]> [Note: Les expressions constantes peuvent être évaluées pendant la traduction. – Note de fin]

Vous pouvez assigner le résultat à la variable constexpr pour être sûr qu’il est évalué au moment de la compilation, nous pouvons le voir dans la référence C ++ 11 de Bjarne Stroustrup qui dit:

En plus de pouvoir évaluer les expressions au moment de la compilation, nous voulons pouvoir exiger que les expressions soient évaluées au moment de la compilation. constexpr devant une définition de variable fait cela (et implique const):

Par exemple:

 constexpr int len1 = length("abcd") ; 

Bjarne Stroustrup donne un résumé du moment où nous pouvons assurer l’évaluation de la compilation dans cette entrée de blog isocpp et dit:

[…] La réponse correcte – comme indiqué par Herb – est que, selon la norme, une fonction constexpr peut être évaluée au moment du compilateur ou à l’exécution, sauf si elle est utilisée comme expression constante, auquel cas elle doit être évaluée lors de la compilation. -temps. Pour garantir l’évaluation à la compilation, nous devons soit l’utiliser lorsqu’une expression constante est requirejse (par exemple, en tant que tableau lié ou en tant qu’étiquette de cas) ou l’utiliser pour initialiser un constexpr. J’espère qu’aucun compilateur qui se respecte ne pourra rater l’occasion d’optimisation de faire ce que j’ai initialement dit: “Une fonction constexpr est évaluée au moment de la compilation si tous ses arguments sont des expressions constantes.”

Donc, cela décrit deux cas où il devrait être évalué au moment de la compilation:

  1. Utilisez-la lorsqu’une expression constante est requirejse, cela semble être n’importe où dans le projet de norme où l’expression shall be ... converted constant expression ou shall be ... constant expression est utilisée, telle qu’un tableau lié.
  2. Utilisez-le pour initialiser un constexpr comme je l’ai indiqué ci-dessus.

Il est très facile de savoir si un appel à une fonction constexpr traduit par une expression de base ou est simplement optimisé:

Utilisez-le dans un contexte où une expression constante est requirejse.

 int main() { constexpr int test_const = length("abcd"); std::array test_const2; } 

Juste une note, que les compilateurs modernes (comme gcc-4.x) font des strlen pour les littéraux de chaîne au moment de la compilation, car ils sont normalement définis comme une fonction insortingnsèque . Sans optimisations activées. Bien que le résultat ne soit pas une constante de compilation.

Par exemple:

 printf("%zu\n", strlen("abc")); 

Résulte en:

 movl $3, %esi # strlen("abc") movl $.LC0, %edi # "%zu\n" movl $0, %eax call printf 

Permettez-moi de proposer une autre fonction qui calcule la longueur d’une chaîne au moment de la compilation sans être récursive.

 template< size_t N > constexpr size_t length( char const (&)[N] ) { return N-1; } 

Jetez un coup d’oeil à cet exemple de code sur ideone .

Il n’y a aucune garantie qu’une fonction constexpr soit évaluée au moment de la compilation, bien que tout compilateur raisonnable le fasse aux niveaux d’optimisation appropriés activés. En revanche, les parameters du modèle doivent être évalués au moment de la compilation.

J’ai utilisé l’astuce suivante pour forcer l’évaluation au moment de la compilation. Malheureusement, cela ne fonctionne qu’avec des valeurs intégrales (c’est-à-dire pas avec des valeurs à virgule flottante).

 template struct static_eval { static constexpr T value = V; }; 

Maintenant, si vous écrivez

 if (static_eval::value > 7) { ... } 

Vous pouvez être sûr que l’instruction if est une constante à la compilation sans surcharge d’exécution.

Une brève explication de l’entrée de Wikipedia sur les expressions constantes généralisées :

L’utilisation de constexpr sur une fonction impose certaines limites à ce que cette fonction peut faire. Tout d’abord, la fonction doit avoir un type de retour non vide. Deuxièmement, le corps de la fonction ne peut pas déclarer de variables ou définir de nouveaux types. Troisièmement, le corps ne peut contenir que des déclarations, des instructions nulles et une seule déclaration de retour. Il doit exister des valeurs d’argument telles que, après la substitution d’arguments, l’expression dans l’instruction return produit une expression constante.

Avoir le mot clé constexpr avant une définition de fonction indique au compilateur de vérifier si ces limitations sont remplies. Si oui et que la fonction est appelée avec une constante, la valeur renvoyée est garantie constante et peut donc être utilisée partout où une expression constante est requirejse.