46 votes

Pourquoi les expressions constantes ont-elles une exclusion pour un comportement indéfini?

J'ai été la recherche de ce qui est permis dans une base constante de l'expression, qui est couvert dans la section 5.19 des expressions Constantes paragraphe 2 du projet de norme C++ qui dit:

Un conditionnel-l'expression est l'une des principales expression constante sauf s'il s'agit de l'un des éléments suivants comme potentiellement évalué sous-expression (3.2), mais les sous-expressions de la logique ET (5.14), OU logique (5.15) et à la condition (5.16) les opérations qui ne sont pas évalués ne sont pas considérés comme des [ Note: Un opérateur surchargé appelle une fonction.-la note de fin ]:

et la liste des exclusions dans les balles qui suit et comprend (c'est moi qui souligne):

- une opération qui aurait un comportement indéfini [ Note: y compris, par exemple, a signé débordement d'entier (Alinéa 5), certains l'arithmétique des pointeurs (5.7), la division par zéro (5.6), ou certaines opérations de décalage (5.8) -la note de fin ];

Hein? Pourquoi faire des expressions constantes besoin de cette clause pour couvrir un comportement indéfini? Est-il quelque chose de spécial à propos des expressions constantes qui exige un comportement non défini d'une façon spéciale tailler dans les exclusions?

Le fait d'avoir cette clause de nous donner tous les avantages ou les outils que nous n'aurions pas sans elle?

Pour référence, cela ressemble à la dernière révision de la proposition pour l' Généralisée des Expressions Constantes.

39voto

Shafik Yaghmour Points 42198

La formulation est en fait le sujet de rapport de défaut #1313 qui dit:

Les exigences pour les expressions constantes ne sont pas actuellement, mais devrait exclure les expressions qui ont un comportement indéfini, comme l'arithmétique des pointeurs quand les liens ne pointent pas vers des éléments de la même matrice.

La résolution de la formulation actuelle que nous avons maintenant, donc, de toute évidence, cela a été prévu, afin de quels outils est-ce à nous donner?

Nous allons voir ce qui se passe lorsque nous essayons de créer un constexpr variable avec une expression qui contient un comportement indéfini, nous utiliserons clang pour tous les exemples suivants. Ce code (voir en direct):

constexpr int x = std::numeric_limits<int>::max() + 1 ;

génère l'erreur suivante:

error: constexpr variable 'x' must be initialized by a constant expression
    constexpr int x = std::numeric_limits<int>::max() + 1 ;
                  ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: value 2147483648 is outside the range of representable values of type 'int'
    constexpr int x = std::numeric_limits<int>::max() + 1 ;
                                       ^

Ce code (voir en direct):

constexpr int x = 1 << 33 ;  // Assuming 32-bit int

produit cette erreur:

error: constexpr variable 'x' must be initialized by a constant expression
    constexpr int x = 1 << 33 ;  // Assuming 32-bit int
             ^   ~~~~~~~
note: shift count 33 >= width of type 'int' (32 bits)
    constexpr int x = 1 << 33 ;  // Assuming 32-bit int
                  ^

et ce code qui a un comportement indéfini dans un constexpr fonction:

constexpr const char *str = "Hello World" ;      

constexpr char access( int index )
{
    return str[index] ;
}

int main()
{
    constexpr char ch = access( 20 ) ;
}

produit cette erreur:

error: constexpr variable 'ch' must be initialized by a constant expression
    constexpr char ch = access( 20 ) ;
                   ^    ~~~~~~~~~~~~

 note: cannot refer to element 20 of array of 12 elements in a constant expression
    return str[index] ;
           ^

Eh bien, c'est utile, le compilateur peut détecter un comportement indéfini dans constexpr, ou au moins ce qu' clang croit est pas défini. Remarque, gcc se comporte de la même, sauf dans le cas d'un comportement indéfini à droite et à gauche maj, gcc se produisent généralement un avertissement dans ces cas, mais il voit encore l'expression de la constante.

Nous pouvons utiliser cette fonctionnalité via SFINAE pour détecter si un ajout de l'expression serait de provoquer un débordement, le inventée suivant l'exemple a été inspiré par la dpj est intelligent de réponse ici:

#include <iostream>
#include <limits>

template <typename T1, typename T2>
struct addIsDefined
{
     template <T1 t1, T2 t2>
     static constexpr bool isDefined()
     {
         return isDefinedHelper<t1,t2>(0) ;
     }

     template <T1 t1, T2 t2, decltype( t1 + t2 ) result = t1+t2>
     static constexpr bool isDefinedHelper(int)
     {
         return true ;
     }

     template <T1 t1, T2 t2>
     static constexpr bool isDefinedHelper(...)
     {
         return false ;
     }
};


int main()
{    
    std::cout << std::boolalpha <<
      addIsDefined<int,int>::isDefined<10,10>() << std::endl ;
    std::cout << std::boolalpha <<
     addIsDefined<int,int>::isDefined<std::numeric_limits<int>::max(),1>() << std::endl ;
    std::cout << std::boolalpha <<
      addIsDefined<unsigned int,unsigned int>::isDefined<std::numeric_limits<unsigned int>::max(),std::numeric_limits<unsigned int>::max()>() << std::endl ;
}

ce qui donne (voir en direct):

true
false
true

Il n'est pas évident que la norme exige que ce comportement, mais apparemment ce commentaire par Howard Hinnant indique qu'il est en effet:

[...] et est également constexpr, sens UB est pris au moment de la compilation

Mise à jour

En quelque sorte j'ai raté Problème de 695 au moment de la Compilation des erreurs de calcul dans constexpr fonctions qui tourne sur le libellé de la section 5 le paragraphe 4 qui avait l'habitude de dire (c'est moi qui souligne à l'avenir):

Si, lors de l'évaluation d'une expression, le résultat n'est pas définie mathématiquement ou pas dans la gamme des représentable valeurs pour son type, le comportement est indéfini, à moins que cette expression apparaît à l'endroit où intégrante expression constante est nécessaire (5.19 [expr.const]), auquel cas le programme est mal formé.

et il ajoute:

conçu comme un Standardese circumlocution pour "évalué au moment de la compilation," un concept qui n'est pas directement définie par la Norme. Il n'est pas clair que cette formulation couvre adéquatement constexpr fonctions.

et plus tard la note dit:

[...]Il y a une tension entre la volonté d'diagnostiquer des erreurs lors de la compilation du temps et de ne pas diagnostiquer des erreurs qui ne sont pas réellement se produire lors de l'exécution.[...]Le consensus de la GTC est qu'une expression comme 1/0 devrait tout simplement être considéré comme non-constante; diagnostic résulterait de l'utilisation de l'expression dans un contexte nécessitant une expression constante.

qui, si je lis correctement confirme l'intention était d'être en mesure de diagnostiquer un comportement non défini au moment de la compilation dans le contexte nécessitant une expression constante.

Nous ne pouvons certainement dire que c'était l'intention, mais il ne suggère fortement qu'il était. La différence dans la façon dont clang et gcc traiter undefined quarts ne laisser de place pour le doute.

21voto

Ben Voigt Points 151460

Lorsque nous parlons d' un comportement indéfini, il est important de rappeler que la Norme quitte le comportement non défini pour ces cas. Il n'est pas interdit implémentations de faire plus de garanties. Par exemple, certaines implémentations peuvent garantir que signée de dépassement d'entier s'enroule autour, tandis que d'autres peuvent garantir la saturation.

Nécessitant des compilateurs pour traiter des expressions constantes impliquant un comportement indéfini serait de limiter les garanties qu'une mise en œuvre pourrait faire, en les limitant à la production d'une certaine valeur sans effets secondaires (ce que la Norme appelle une valeur indéterminée). Qui exclut beaucoup de l'étendue des garanties trouvé dans le monde réel.

Par exemple, certains de la mise en œuvre ou compagnon standard (c'est à dire POSIX) peut définir le comportement de l'intégrale de la division par zéro pour générer un signal. C'est un effet secondaire qui serait perdue si l'expression ont été calculés au moment de la compilation à la place.

Ainsi, ces expressions sont rejetées au moment de la compilation pour éviter la perte d'effets secondaires dans l'environnement d'exécution.

6voto

cmaster Points 7460

L'exclusion des comportements indéfinis des expressions constantes présente un autre avantage: les expressions constantes doivent, par définition, être évaluées par le compilateur au moment de la compilation. Autoriser une expression constante à invoquer un comportement non défini permettrait au compilateur lui-même d'afficher un comportement non défini. Et un compilateur qui formate votre disque dur parce que vous compilez du code malveillant n’est pas quelque chose que vous souhaitiez.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X