Calcul de la longueur d'une chaîne C au moment de la compilation. Est-ce vraiment un constexpr?

94

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 <cstdio>

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 lengthfonction sera évaluée lors de la compilation?

Si cela est vrai, la porte pour les calculs de littéraux de chaîne au moment de la compilation vient de s'ouvrir pour moi ... par exemple, je peux calculer des hachages au moment de la compilation et bien d'autres ...

Mircea Ispas
la source
3
Tant que le paramètre est une expression constante, il doit l'être.
chris
1
@chris Y a-t-il une garantie que quelque chose qui peut être une expression constante doit être évalué au moment de la compilation lorsqu'il est utilisé dans un contexte qui ne nécessite pas d'expression constante?
TC
12
BTW, y compris <cstdio>puis appeler ::printfn'est pas portable. La norme exige seulement <cstdio>de fournir std::printf.
Ben Voigt
1
@BenVoigt Ok, merci de l'avoir signalé :) Au départ, j'ai utilisé std :: cout, mais le code généré était assez gros pour trouver les valeurs réelles :)
Mircea Ispas
3
@Felics J'utilise souvent godbolt lorsque je réponds à des questions concernant l'optimisation et l'utilisation printfpeut conduire à beaucoup moins de code à traiter.
Shafik Yaghmour

Réponses:

76

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

[...]> [Remarque: Les expressions constantes peuvent être évaluées pendant la traduction. — note de fin]

Vous pouvez affecter le résultat à une constexprvariable pour être sûr qu'il est évalué au moment de la compilation, nous pouvons le voir à partir de la référence C ++ 11 de Bjarne Stroustrup qui dit (c'est moi qui souligne ):

En plus de pouvoir évaluer les expressions au moment de la compilation, nous voulons être en mesure d' 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 du temps de compilation dans cette entrée de blog isocpp et dit:

[...] La bonne réponse - comme indiqué par Herb - est que selon la norme une fonction constexpr peut être évaluée au moment du compilateur ou à l'exécution à moins qu'elle ne soit utilisée comme une expression constante, auquel cas elle doit être évaluée à la compilation -temps. Pour garantir l'évaluation au moment de la compilation, nous devons soit l'utiliser là où une expression constante est requise (par exemple, en tant que tableau lié ou en tant qu'étiquette de cas) ou l'utiliser pour initialiser une constexpr. J'espère qu'aucun compilateur qui se respecte ne manquerait l'opportunité d'optimisation de faire ce que j'ai dit à l'origine: "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-le là où une expression constante est requise, cela semblerait être n'importe où dans le projet de norme où la phrase shall be ... converted constant expressionou shall be ... constant expressionest utilisée, comme un tableau lié.
  2. Utilisez-le pour initialiser un constexprcomme je l'ai décrit ci-dessus.
Shafik Yaghmour
la source
4
Cela dit, en principe, un compilateur a le droit de voir un objet avec un lien interne ou sans lien avec constexpr int x = 5;, observez qu'il n'a pas besoin de la valeur au moment de la compilation (en supposant qu'elle ne soit pas utilisée comme paramètre de modèle ou quoi que ce soit) et émet en fait code qui calcule la valeur initiale au moment de l'exécution en utilisant 5 valeurs immédiates de 1 et 4 opérations d'addition. Un exemple plus réaliste: le compilateur peut atteindre une limite de récursivité et reporter le calcul jusqu'à l'exécution. À moins que vous ne fassiez quelque chose qui force le compilateur à utiliser réellement la valeur, «garanti d'être évalué au moment de la compilation» est un problème de QOI.
Steve Jessop
@SteveJessop Bjarne semble utiliser un concept qui n'a pas d'analogue que je peux trouver dans le projet de norme qui est utilisé comme moyen d' expression constant évalué à la traduction. Il semblerait donc que la norme n'énonce pas explicitement ce qu'il dit, alors j'aurais tendance à être d'accord avec vous. Bien que Bjarne et Herb semblent d'accord sur ce point, cela pourrait indiquer qu'il est juste sous-spécifié.
Shafik Yaghmour
2
Je pense qu'ils ne considèrent tous les deux que des "compilateurs qui se respectent", par opposition au compilateur conforme aux normes mais volontairement obstructif que je suppose. C'est utile comme moyen de raisonner sur ce que la norme garantit réellement , et pas grand-chose d'autre ;-)
Steve Jessop
3
@SteveJessop Compilateurs volontairement obstructifs, comme l'infâme (et malheureusement inexistant) Hell ++. Une telle chose serait en fait idéale pour tester la conformité / la portabilité.
Angew n'est plus fier de SO
En vertu de la règle as-if, même utiliser la valeur comme constante de temps de compilation apparente ne suffit pas: le compilateur est libre d'expédier une copie de votre source et de la recompiler au moment de l'exécution, ou d'effectuer un calcul pour déterminer le type d'un variable, ou tout simplement réexécutez inutilement votre constexprcalcul par pur mal. Il est même gratuit d'attendre 1 seconde par personnage dans une ligne de source donnée, ou de prendre une ligne de source donnée et de l'utiliser pour semer une position d'échecs, puis de jouer des deux côtés pour déterminer qui a gagné.
Yakk - Adam Nevraumont
27

Il est vraiment facile de savoir si un appel à une constexprfonction aboutit à une expression constante de base ou s'il est simplement optimisé:

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

int main()
{
    constexpr int test_const = length("abcd");
    std::array<char,length("abcdefgh")> test_const2;
}
Ben Voigt
la source
4
... et compilez avec -pedantic, si vous utilisez gcc. Sinon, vous n'obtenez aucun avertissement ni erreur
BЈовић
@ BЈовић Ou utilisez-le dans un contexte où GCC n'a aucune extension susceptible de gêner, comme un argument de modèle.
Angew n'est plus fier de SO
Un hack enum ne serait-il pas plus fiable? Tels que enum { Whatever = length("str") }?
Dentranchante
18
Digne de mention eststatic_assert(length("str") == 3, "");
chris
8
constexpr auto test = /*...*/;est probablement la plus générale et la plus simple.
TC
19

Juste une note, que les compilateurs modernes (comme gcc-4.x) le font strlenpour les chaînes littérales au moment de la compilation car il est normalement défini comme une fonction intrinsèque . Sans optimisations activées. Bien que le résultat ne soit pas une constante de temps 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
Maxim Egorushkin
la source
Remarque, cela fonctionne parce que strlenc'est une fonction intégrée, si nous l'utilisons, -fno-builtinsil revient à l'appeler au moment de l'exécution, regardez-le en direct
Shafik Yaghmour
strlenest constexprpour moi, même avec -fno-nonansi-builtins(il semble -fno-builtinsqu'il n'existe plus dans g ++). Je dis "constexpr", car je peux faire ça template<int> void foo();et foo<strlen("hi")>(); g ++ - 4.8.4
Aaron McDaid
18

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 œil à cet exemple de code sur ideone .

user2436830
la source
4
Il peut ne pas être égal à strlen en raison de '\ 0' intégré: strlen ("hi \ 0there")! = Length ("hi \ 0there")
unkulunkulu
C'est la bonne façon, ceci est un exemple dans Effective Modern C ++ (si je me souviens bien). Cependant, il y a une belle classe de chaîne qui est entièrement constexpr voir cette réponse: str_const de Scott Schurr , peut-être que ce sera plus utile (et moins de style C).
QuantumKarl
@MikeWeir Ops, c'est étrange. Voici différents liens: lien vers la question , lien vers l'article , lien vers la source sur git
QuantumKarl
maintenant yow do: char temp[256]; sprintf(temp, "%u", 2); if(1 != length(temp)) printf("Your solution doesn't work"); ideone.com/IfKUHV
Pablo Ariel
7

Il n'y a aucune garantie qu'une constexprfonction est évaluée au moment de la compilation, bien que tout compilateur raisonnable le fasse aux niveaux d'optimisation appropriés activés. D'un autre côté, les paramètres 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<typename T, T V>
struct static_eval
{
  static constexpr T value = V;
};

Maintenant, si tu écris

if (static_eval<int, length("hello, world")>::value > 7) { ... }

vous pouvez être sûr que l' ifinstruction est une constante de compilation sans surcharge au moment de l'exécution.

5gon12eder
la source
8
ou utilisez simplement std :: integr_constant <int, length (...)> :: value
Mircea Ispas
1
L'exemple est un peu inutile car lenêtre des constexprmoyens lengthdoit de toute façon être évalué au moment de la compilation.
chris
Je ne savais @ Chris pas doit être, bien que je l' ai fait observer qu'il est avec mon compilateur.
5gon12eder
Ok, selon la majorité des autres réponses, j'ai donc modifié l'exemple pour qu'il soit moins inutile. En fait, c'était une ifcondition (où il était essentiel que le compilateur élimine le code mort) pour laquelle j'ai initialement utilisé l'astuce.
5gon12eder
1

Une brève explication de l'entrée de Wikipédia sur les expressions constantes généralisées :

L'utilisation de constexpr sur une fonction impose certaines limitations à ce que cette fonction peut faire. Tout d'abord, la fonction doit avoir un type de retour non void. 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 instruction return. Il doit exister des valeurs d'argument telles que, après la substitution d'argument, l'expression dans l'instruction de retour produise une expression constante.

Le fait d'avoir le constexprmot - clé avant une définition de fonction indique au compilateur de vérifier si ces limitations sont respectées. 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 requise.

Kaedinger
la source
Ces conditions ne garantissent pas que la valeur renvoyée est constante . Par exemple, la fonction peut être appelée avec d'autres valeurs d'argument.
Ben Voigt le
À droite, @BenVoigt. Je l'ai édité pour être appelé avec une expression constante.
kaedinger le