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)>{})
^
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?
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:
pred
de u
à partir du paramètre f
de t
pred
, ce qui crée trivialement une bool
valeur true
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éralu
à 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 ).
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)>{})
{
}
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.
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.