pourquoi la déduction de type de retour ne peut pas prendre en charge SFINAE avec std :: is_invocable_v
il y a quelques foncteurs avec déduction de type de retour , y compris les expressions lambda .
constexpr auto a = [](auto&& x){ return x + 1; };
struct X{
template<typename T>
auto operator()(T&& x){
return x + 1;
}
};
et puis, j'ai 2 fonctions pour vérifier si les arguments peuvent être appliqués à ces foncteurs, par std::is_invocable_v
et SFINAE .
template<typename F, typename T, std::enable_if_t<std::is_invocable_v<F, T>, int> = 0>
void foo(T a){
std::cout << "yes" << std::endl;
}
template<typename F, typename T, std::enable_if_t<!std::is_invocable_v<F, T>, int> = 0>
void foo(T a){
std::cout << "no" << std::endl;
}
enfin, j'utilise foo<X>(0)
ou foo<decltype(a)>(0)
, et ça marche bien et dit "oui" car le contrôle est passé. mais lorsque j'utilise foo<X>((void*)0)
ou foo<decltype(a)>((void*)0)
, je reçois une erreur de compilation au lieu de "non".
La substitution se produit dans
1. tous les types utilisés dans le type de fonction (qui comprend le type de retour et les types de tous les paramètres )
...
il semble que ces foncteurs acceptent des arguments de n'importe quel type, puis lancent une erreur s'ils x + 1
sont mal formés. mais, le type de retour de operator()
est déduit par x + 1
, ce qui signifie qu'il dépend du type de l'argument T
. lorsque le std::is_invocable_v
est instancié, T
est remplacé par void*
, puis la signature de operator()
a un type de retour non valide . est-ce un échec de remplacement ?
pour clarifier cette question, je définis ces 2 foncteurs:
struct Y{
template<typename T>
decltype(auto) operator()(T&& x){
return x + 1;
}
};
struct Z{
template<typename T>
auto operator()(T&& x)->decltype(x + 1){
return x + 1;
}
};
Si le type de retour est
decltype(auto)
, le type de retour est celui qui serait obtenu si l'expression utilisée dans l'instruction de retour était encapsuléedecltype
.
mais pourquoi foo<Z>((void*)0)
dit «non» mais foo<Y>((void*)0)
aboutit à une erreur?
Réponses
C'est [dcl.spec.auto] / 11 .
Les types de retour déduits ne sont pas compatibles avec SFINAE, dans le sens où l'interrogation du type de retour d'un appelable basé sur un modèle (modèle de fonction / lambda générique / foncteur avec modèle operator()(...)
) qui exploite la déduction du type de retour nécessite l'instanciation de la spécialisation particulière de l'appelable, comme la définition de la spécialisation est nécessaire pour en déduire le type de retour:
[dcl.spec.auto] / 11 La déduction de type de retour pour un modèle de fonction avec un espace réservé dans son type déclaré se produit lorsque la définition est instanciée même si le corps de la fonction contient une instruction de retour avec un opérande non dépendant du type. [ Note: Par conséquent, toute utilisation d'une spécialisation du modèle de fonction provoquera une instanciation implicite. Les erreurs qui résultent de cette instanciation ne sont pas dans le contexte immédiat du type de fonction et peuvent entraîner une mauvaise formation du programme ([temp.deduct]). - note de fin] [Exemple:
template <class T> auto f(T t) { return t; } // return type deduced at instantiation time typedef decltype(f(1)) fint_t; // instantiates f<int> to deduce return type template<class T> auto f(T* t) { return *t; } void g() { int (*p)(int*) = &f; } // instantiates both fs to determine return types, // chooses second
- fin d'exemple]
En raison de l'implémentation de std::is_invocable
, qui s'applique decltype
à l'expression (non évaluée) d'invocation de la spécialisation appelable (pour trouver le type de retour), la déduction du type de retour est déclenchée pour la spécialisation, ce qui nécessite l'instanciation de la spécialisation, ce qui entraîne ce cas, comme souligné dans la note (non normative) ci-dessus, le programme est mal formé.