29
votes

Qu'est-ce qui détermine si une fonction constexpr est une expression constante?

(le compilateur utilisé est GCC avec C ++ 17 pour autant que je sache (difficile à trouver cela dans Visual Studio))

#include <iostream>

using namespace std;

void increment( int& v )
{
    ++v;
}

int constexpr f()
{
    int v = 0;
    for( int i = 0; i < 1; ++i )
    {
        increment( v );
    }   
    return v;
}

int main( )
{
    cout << f( ) << '\n';
}

Le code ci-dessus donne l'erreur sur la compilation: p>

La fonction constexpr 'f' ne peut pas entraîner une expression constante.

Si je comprends bien, c'est parce que la fonction incrément n'est pas une constexpr. Ce qui me confond, c'est que le code suivant se compile bien:

#include <iostream>

using namespace std;

void increment( int& v )
{
    ++v;
}

int constexpr f()
{
    int v = 0;
    increment( v );
    return v;
}

int main( )
{
    cout << f( ) << '\n';
}

Ce code est fonctionnellement le même et il compile, même si l'incrément n'est toujours pas un constexpr. Je ne comprends pas comment il est possible qu'une boucle à travers la plage [0, 1) fait réaliser le compilateur Tout le monde peut donner un aperçu de la constexpr en C ++ et cette incohérence apparente, je l'apprécierais grandement.


10 commentaires

FWIW, mêmes résultats sur Godbolt (GCC Trunk, C ++ 17), compiles: godbolt.org/z/sdm9zf < / a> Erreur: godbolt.org/z/3sebfh


FWIW, votre deuxième exemple ne compile pas dans ma copie de GCC. J'obtiens cette erreur: "Erreur: appel à la fonction non-constexpr" vide incrément (int &) "


@Dai De quelle version s'agit-il?


Tout avant GCC9 ne parvient pas à compiler ici . Je ne sais pas ce qui se passe, mais à mon humble avis, cela devrait être une erreur et Clang est d'accord


Utilisation de clang -std = c ++ 1z file.cpp -o file.o Le deuxième exemple ne compile pas. Clang version 10.0.1.


Cela est peut-être parce que sans la boucle, l'analyseur peut savoir qu'il ne sera jamais un constexpr. Avec la boucle (au sens générique, s'il ne regarde pas les limites de la boucle), il pense qu'il est possible que la fonction incrément ne soit pas appelée et donc f pourrait être en mesure d'avoir une valeur constante.


Votre deuxième code ne se compile pas avec mon Clang ++ (7.0.1), "Error: Consxpr La fonction ne produit jamais une expression constante [-WinValid-Constexpr]". Mais je me compiler avec "-ansi -centantic -wall"


FWIW, même si conforme, je considérerais cela comme un bug dans GCC. Je veux dire qu'ils ont réussi à publier un diagnostic avant 9 ans, ce qui est bien même si ce n'est pas réduit. Pourquoi plus?


@ idclev463035818: les développeurs de GCC pourraient considérer cela un bug si quelqu'un se soucie de le signaler. Je me demandais si peut-être l'optimisation / l'inclinaison se produisait avant de vérifier la validité de constexpr, mais non, toujours pas de diagnostic avec -o0 . (Peut-être lié: même lorsque f () est une fonction valide constexpr , GCC choisit de ne pas l'évaluer au moment de la compilation à - O0 . godbolt.org/z/mjowoe . J'ai simplifié la sortie ASM en utilisant < Code> return f (); au lieu de cout <<. Mais le gcc plus ancien comme 7.5 fait cela aussi, et non inclinant ou propagant constant (sauf lorsqu'il est forcé) est juste normal -o0 comportement.)


Juste une supposition, dans le deuxième exemple, le compilateur peut d'abord en informer l'incrément de mini-fonction vide (int & v), donc peu importe que ce ne soit pas un constexpr ... après avoir inclus


4 Réponses :


0
votes

La norme nécessite qu'une fonction constexpr soit réellement évaluable au moment de la compilation pour un ensemble de paramètres mais pas tous. Il ne nécessite pas que les compilateurs diagnostiquent une fonction constexpr faisant certaines choses qui pourraient être du temps non compile dans certaines circonstances, ou même si une telle fonction a un tel ensemble de paramètres. Cela les évite d'avoir à résoudre le problème d'arrêt.


12 commentaires

Mais il n'y a pas de paramètres dans aucun d'entre eux. La boucle est exécutée exactement une fois, non?


f n'a qu'un seul ensemble de paramètres - l'ensemble vide.


Ce n'est pas le propos. Parce qu'il serait impossible pour un compilateur de prouver de manière fiable si une fonction pourrait être évaluée au moment de la compilation, la norme ne le nécessite pas. Aucune exception n'est faite pour des cas faciles spécifiques.


Je veux dire sans paramètres, soit ils sont évaluables au moment de la compilation pour tous les paramètres possibles, soit pour aucun. Je me trompe peut-être, mais pourquoi?


Le compilateur peut et sait que pour (int i = 0; i <1; ++ i) exécutera une fois, car tout est de compilation d'informations sur le temps. Cela signifie que incrément doit être appelé, et comme il n'est pas constexpr , f n'est pas non plus.


Non, tu as raison. Mais le comité des normes a choisi de ne pas compliquer la langue en incluant un ensemble exhaustif de cas de bord étrange où la non-contestexpr-ness pourrait être directement prouvée et exigeant que le compilateur diagnostique cet ensemble. (Je pense que c'était la bonne décision de leur part.)


Êtes-vous en train de dire que constexpr est une promesse, dans un sens comme const ? Je promets de ne pas appliquer un const_cast et de modifier quelque chose qui est const / Je prétend qu'il existe des paramètres qui permettent d'évaluer la fonction au moment de la compilation?


Je dis que le compilateur est autorisé à ne pas être assez intelligent pour diagnostiquer cette erreur.


Merci pour votre patience. Maintenant, je me demande à quoi servent les downvotes. Peut-être que la première phrase est trop importante, pour moi, elle a causé un peu de confusion


J'ai été impressionné par le taux d'activité de vote: D


" Il ne nécessite pas de compilateurs pour diagnostiquer une fonction constexpr faisant des choses non compiles ". Ce n'est pas correct. Il y a des choses de temps non compilées que le compilateur est nécessaire pour diagnostiquer, c'est juste que f n'en fait aucun.


@Sneftel ouais c'était assez de montagnes russes vers le bas: D



2
votes

vous n'êtes pas vraiment "appeler" f au moment de la compilation.

Si votre fonction principale comprenait: static_assert (f () == 1, "f () renvoyé 1"); Je soupçonne que vous obtiendrez une erreur "f () n'est pas une expression constante".

Voici un Question connexe


1 commentaires

En effet, godbolt.org/z/dar4gv Affiche Erreur: Condition non-Constante pour une assertion statique au lieu de simplement faire une propagation constante. Ainsi que les plus révélateurs: dans 'Consxpr' Expansion of 'F ()' / Erreur: appelez vers non'Constexpr 'fonction' void incrément (int &) ' . Ainsi, GCC applique le concept d'évaluation de la fonction dans le contexte d'une expression / contexte constexpr, étant beaucoup plus permissive autrement. Il s'agit peut-être d'une extension C ++ utile pour les fonctions avec des args, permettant aux appels aux fonctions non-constexpr protégées par un si cela n'est vrai que dans les cas non conformes.



13
votes

Les deux programmes sont "mal formulés sans diagnostic requis", par [dcl. constexpr] / 6 :

pour une fonction Consxpr ou un constructeur Consxpr qui n'est ni par défaut ni un modèle, si aucune valeur d'argument n'existe telle qu'une invocation de la fonction ou du constructeur pourrait être une sous-expression évaluée d'une expression constante de base, ou, pour un constructeur, un constructeur Sous-expression évaluée de l'initialisation complète d'expression d'un objet constant initialisé ( [BASIC .start.static] ), le programme est mal formé, aucun diagnostic requis.

Il est un peu étrange que GCC ne remarque pas le problème avec le deuxième programme, mais il est toujours conforme.

Remarque Un diagnostic serait nécessaire si f étaient utilisés dans un contexte qui nécessite réellement une expression constante, par exemple constexpr int n = f (); .

Certaines choses ne sont jamais autorisées dans une fonction constexpr. Ceux-ci nécessitent un diagnostic (généralement un message d'erreur), même si la fonction n'est jamais utilisée dans une expression constante - voir Réponse de Cigien . Mais les programmes de la question ne violent aucune de ces règles plus strictes.


12 commentaires

Cette réponse est incorrecte, ou du moins incomplète. Aucune valeur d'argument n'existe qui permet constexpr int g () {goto x; } pour être une "sous-expression évaluée ...", mais g n'est certainement pas IFNDR, et le compilateur est requis pour diagnostiquer cela.


@cigien vous êtes sûr? ce ressemble à des exemples de la PO et dit que c'est ifndr


@cigien goto n'est explicitement pas autorisé, l'OP, ne l'utilise pas cependant.


@Nathanoliver Bien sûr, le code OP est ifndr, mais c'est parce que la définition de f ne viole pas l'une des exigences diagnostiquables. Ce n'est pas automatiquement IFNDR en raison des valeurs "Aucune argument n'existe telle que ..." comme la réponse l'indique.


@cigien Chaque exigence de la norme doit être satisfaite indépendamment, bien sûr. Ils ne sont pas non plus mal formés en raison d'un appel de fonction surchargé ambigu, ou etc. ou etc. Ce que j'ai cité explique que le programme n'est pas bien formé, et je pense que c'est l'exigence la plus applicable pour la question. Votre devis sur certains cas similaires qui nécessitent inconditionnellement un diagnostic est un ajout utile à cela.


@cigien Cela aborde également la confusion dans la question OP "... fait réaliser le compilateur que la fonction f est en fait une constexpr."


Ok, je pense que je vois ce que tu dis. Mon sentiment est que l'accent devrait être l'inverse. Il y a des exigences diagnostiquables sur les fonctions constexpr, et comme aucune d'entre elles n'est violée, elle retombe à IFNDR, selon le texte que vous avez cité. Je préfère indiquer, si possible, pourquoi le programme n'est pas mal formé en premier.


Voici un Exemple de ce que je veux dire. Votre réponse serait complètement erronée pour cette question, mais pour autant que je sache, il n'y a aucune raison que votre réponse ne serait pas applicable à cette question.


@cigien a ajouté une note et un lien vers votre réponse.


@Cigien: IFNDR gagne si vous le violez ainsi qu'une autre règle. Dans ce cas, je dirais qu'il s'agit d'un défaut de libellé, et que «et aucun appel à la fonction n'est évalué dans une expression constante» devrait être interpolée. Il existe bien sûr également des cas où la violation d'une règle diagnostiquable empêche l'interprétation du programme, où nous devons pour la santé mentale qu'aucune règle non diagnosable n'est violée par tout ce que le programme mal formé signifie.


@Davisherring Je suis un peu confus. Êtes-vous en train de dire que le code lié dans mon commentaire précédent est IFNDR car il viole [Decl.Constexpr / 6]? Et donc un diagnostic n'est pas requis?


@cigien: une étiquette ne viole pas [expr.const] / 2, donc la règle IFNDR ne s'applique pas dans ce cas. Cependant, IFNDR gagne si vous avez une fonction constexpr qui n'a pas de valeurs d'argument valides même si vous l'appelez dans une expression constante. Les implémentations seront dans la pratique diagnostiquer cela, car elle est gratuite lors de cette évaluation, mais (malheureusement) ils n'ont pas .



6
votes

Puisque vous n'appelez pas f dans une expression constante, votre question vous demande si le compilateur est requis pour diagnostiquer que f peut Ce n'est pas appelé dans une expression constante, basée uniquement sur sa définition .

Les exigences de la fonction de définition d'une fonction constexpr sont énumérées ici :

La définition d'une fonction constexpr doit satisfaire aux exigences suivantes:

(3.1) Son type de retour (le cas échéant) doit être un type littéral;

(3.2) Chacun de ses types de paramètres doit être un type littéral;

(3.3) Ce ne sera pas une coroutine;

(3.4) Si la fonction est un constructeur ou un destructeur, sa classe n'a pas de classes de base virtuelles;

(3.5) son corps de fonction ne fera pas enfermer

(3.5.1) une déclaration goto,

(3.5.2) une étiquette d'identifiant,

(3.5.3) une définition d'une variable de type non littéral ou de durée de stockage statique ou de thread.

Comme on peut le voir, la définition de f ne viole aucune des exigences de la liste. Ainsi, un compilateur se conforme s'il choisit de ne pas diagnostiquer cela.

Comme l'a souligné dans Réponse d'Aschepler , constexpr Fonctions comme f qui ne peut pas être appelé dans une expression constante, mais qui ne sont pas diagnostiqués en tant que tels, sont considérés comme mal formés-diagnostic.


11 commentaires

Donc, la pièce avec "ne doit appeler que d'autres fonctions constexpr" n'est plus vraie?


@Surt De quel texte parlez-vous exactement?


Semblent se rappeler que la première version de Constexpr était extrêmement limitée et que l'une des exigences était qu'elle appelait uniquement les autres fonctions constexpr.


@Surt Oh, c'est C ++ 11 Je pense. Vous pouvez faire beaucoup plus maintenant, surtout à partir de C ++ 20 :)


@Surt Une fonction Consxpr peut appeler les fonctions non-Consexpr en général, mais lorsqu'elle est utilisée dans un contexte nécessitant une expression constante, toutes les fonctions réellement appelées doivent être marquées constexpr. C'est maintenant une exigence sur "Expression constante" plutôt que sur la "fonction constexpr".


Où dans la norme dit-il de quelle version il s'agit? Subtitle dit "généré 2020", mais je suppose que c'est C ++ 17, non?


@ idclev463035818 C'est le projet de travail, il y a donc C ++ 20.


Notez que la question demande spécifiquement C ++ 17. Quelque chose a-t-il changé entre cette partie?


@ idclev463035818 ah, vrai. Je ne me souviens pas si quelque chose a changé entre les deux. Je vais jeter un coup d'œil et modifier si quelque chose de significatif était changé. S'il n'y a que quelques diagnostics supplémentaires, je vais laisser la réponse, car je ne pense pas que l'OP se soucie exactement de la spécification C ++ 17.


En fait, je ne crois pas que cela changera beaucoup sur la réponse, juste en demandant à la curiosirie


@ idclev463035818 Non, c'est une bonne question, un lot de choses a changé de constexpr, donc cela vaut la peine de vérifier. Bien que je suis à peu près sûr qu'il n'y a pas de changements pertinents ici.