pourquoi la déduction de type de retour ne peut pas prendre en charge SFINAE avec std :: is_invocable_v

Nov 19 2020

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_vet 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 + 1sont 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_vest instancié, Test 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

3 dfrib Nov 19 2020 at 15:29

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é.