web-dev-qa-db-fra.com

Utilisation de l'argument de fonction dans le cadre d'une expression constante - gcc vs clang

Considérez l'extrait de code suivant:

template <bool> struct B { };

template <typename T>
constexpr bool pred(T t) { return true; } 

template <typename T>
auto f(T t) -> decltype(B<pred(t)>{})
{
}
  • clang ++ (trunk) compile le code

  • g ++ (trunk) ne parvient pas à la compilation avec l'erreur suivante:

    src:7:34: error: template argument 1 is invalid
    auto f(T t) -> decltype(B<pred(t)>{})
                                    ^
    
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:25: error: invalid template-id
    auto f(T t) -> decltype(B<pred(t)>{})
                            ^
    
    src:7:36: error: class template argument deduction failed:
    auto f(T t) -> decltype(B<pred(t)>{})
                                        ^
    
    src:7:36: error: no matching function for call to 'B()'
    src:1:24: note: candidate: 'template<bool <anonymous> > B()-> B<<anonymous> >'
    template <bool> struct B { };
                            ^
    
    src:1:24: note:   template argument deduction/substitution failed:
    src:7:36: note:   couldn't deduce template parameter '<anonymous>'
    auto f(T t) -> decltype(B<pred(t)>{})
                                        ^
    

    exemple live sur godbolt.org


Même si le diagnostic de g ++ est trompeur, je suppose que le problème ici est que t n'est pas une expression constante. Changer le code en ...

decltype(B<pred(T{})>{})

... corrige l'erreur de compilation sur g ++: exemple live sur godbolt.org


Quel compilateur se comporte correctement ici? 

24
Vittorio Romeo

GCC a tort. Aucune règle n'empêche d'utiliser les paramètres d'une fonction dans une expression constante de cette manière.

Cependant, vous ne pouvez pas utiliser le valeur du paramètre dans un tel contexte et l'ensemble des types T pour lesquels f est appelable est assez restreint. Pour voir pourquoi, nous devons considérer quelles constructions seront évaluées lors de l'évaluation de l'expression pred(t):

// parameters renamed for clarity
template <typename U>
constexpr bool pred(U u) { return true; } 

template <typename T>
auto f(T t) -> decltype(B<pred(t)>{});

La sémantique de l'évaluation pour l'appel pred(t) est la suivante:

  1. copier-initialiser le paramètre pred de u à partir du paramètre f de t
  2. évaluer le corps de pred, ce qui crée trivialement une bool valeur true
  3. détruire u

Donc, f n'est appelable que pour les types T pour lesquels ce qui précède implique uniquement des constructions valides lors de l'évaluation constante (voir [expr.const] p2 pour les règles). Les exigences sont:

  • T doit être un type littéral
  • copie-initialisation de u à partir de t doit être une expression constante, et en particulier, ne doit pas effectuer de conversion lvalue en rvalue sur un membre de t (car leurs valeurs ne sont pas connues) et ne doit nommer aucun membre de référence de t

En pratique, cela signifie que f est appelable si T est un type de classe vide avec un constructeur de copie par défaut ou si T est un type de classe dont le constructeur de copie est constexpr et ne lit aucun membre de son argument, ou (étrangement) si T est std::nullptr_t (bien que clang obtienne actuellement le nullptr_t mal le cas ).

3
Richard Smith

t n'est pas une valeur constexpr, cette moyenne pred(t) n'est pas trop constexpr . Vous ne pouvez pas l'utiliser dans B<pred(t)> car elle nécessite constexpr.

Cette version compile correctement:

template <bool> struct B { };

template <typename T>
constexpr bool pred(T t) { return true; } 

template <typename T, T t>
auto f() -> decltype(B<pred(t)>{})
{
}

https://godbolt.org/g/ydbj1X

Un autre code valide est:

template <typename T>
auto f(T t) -> decltype(pred(t))
{
}

En effet, vous n'évaluez pas pred(t) uniquement, vous obtenez des informations sur le type .B<pred(t)> doit évaluer pred(t) sinon vous obtiendrez B<true> ou B<false>, pour toute valeur normale impossible à obtenir.

std::integral_constant<int, 0>{} peut fonctionner dans le cas Clang est probablement dû au fait que sa valeur est intégrée au type et est toujours identique. Si on change un peu de code:

template <typename T>
auto f(T t) -> decltype(B<pred(decltype(t){})>{})
{
    return {};
}

clang et GCC le compilent tous deux, dans le cas où std::integral_constant, t et decltype(t){} ont toujours la même valeur.

0
Yankes

Le compilateur attend un paramètre dans ce contexte car il doit évaluer le type de fonction complet (modèle surchargé). Étant donné l'implémentation de pred, toute valeur fonctionnerait à cet endroit. Ici, il lie le type de modèle du paramètre f à l'argument . Le compilateur g ++ semble simplifier l'hypothèse qu'une fonction template constexpr sera modifiée d'une manière ou d'une autre par n'importe quel paramètre, à moins qu'il ne s'agisse également de const, démontré, et clang accepte, n’est pas nécessairement le cas.

Tout dépend de la profondeur à laquelle l'implémentation de la fonction doit être marquée par la compilation de la fonction comme étant non-const en raison de la contribution non-const à la valeur de retour.

Vient ensuite la question de savoir si la fonction est instanciée et si le compilateur doit réellement compiler le code et effectuer une analyse de modèle qui, du moins avec g ++, semble constituer un niveau de compilation différent.

Ensuite, je suis passé à la norme et ils ont gentiment permis au rédacteur du compilateur de faire exactement cette hypothèse simplificatrice et l'instanciation de la fonction de modèle ne devrait fonctionner que pour f<const T> ou f <const T&>.

les fonctions constexpr` doivent avoir: chacun de ses paramètres doit être LiteralType

Donc, le code du modèle doit être compilé mais échouer s'il est instancié avec un T. non constant.

0
dex black