dlaczego dedukcja typu zwracanego nie może obsługiwać SFINAE z std :: is_invocable_v

Nov 19 2020

istnieje kilka funktorów z dedukcją typu zwracanego , w tym wyrażenia lambda .

constexpr auto a = [](auto&& x){ return x + 1; };
struct X{
    template<typename T>
    auto operator()(T&& x){
        return x + 1;
    }
};

a następnie mam dwie funkcje, które sprawdzają, czy argumenty można zastosować do tych funktorów, przez std::is_invocable_vi 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;
}

na koniec używam foo<X>(0)or foo<decltype(a)>(0), i działa dobrze i mówi „tak”, ponieważ sprawdzenie jest pomyślne. ale kiedy używam foo<X>((void*)0)lub foo<decltype(a)>((void*)0), otrzymuję błąd kompilacji zamiast „nie”.

Podstawienie następuje w

1. wszystkie typy używane w typie funkcji (w tym typ zwracany i typy wszystkich parametrów )

...

wygląda na to, że te funktory przyjmą argumenty dowolnego typu, a następnie wyświetlą błąd, jeśli x + 1są źle sformułowane. ale typ zwracany operator()jest wywnioskowany przez x + 1, co oznacza, że ​​zależy od typu argumentu T. kiedy std::is_invocable_vtworzony jest egzemplarz, Tjest zastępowany przez void*, a następnie sygnatura operator()ma nieprawidłowy typ zwrotu . czy to zastępcza awaria ?


aby wyjaśnić to pytanie, zdefiniuję te 2 funktory:

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;
    }
};

Jeśli typ zwracany to decltype(auto), typ zwracany jest taki, jaki zostałby uzyskany, gdyby wyrażenie użyte w instrukcji return zostało opakowanedecltype .

ale dlaczego foo<Z>((void*)0)mówi „nie”, ale foo<Y>((void*)0)powoduje błąd?

Odpowiedzi

3 dfrib Nov 19 2020 at 15:29

To jest [dcl.spec.auto] / 11 .

Dedukowane typy zwracane nie są przyjazne dla SFINAE, w tym sensie, że zapytanie o zwracany typ elementu wywoływanego z szablonu (szablon funkcji / ogólna lambda / funktor z szablonem operator()(...)), które wykorzystuje dedukcję typu zwracanego, wymaga wystąpienia określonej specjalizacji wywoływalnej, jako definicji specjalizacji jest potrzebna, aby wydedukować typ zwrotu:

[dcl.spec.auto] / 11 Odliczenie typu zwracanego dla szablonu funkcji z symbolem zastępczym w zadeklarowanym typie występuje, gdy tworzona jest instancja definicji, nawet jeśli treść funkcji zawiera instrukcję return z operandem niezależnym od typu. [  Uwaga: Dlatego każde użycie specjalizacji szablonu funkcji spowoduje niejawną instancję. Wszelkie błędy, które wynikają z tej instancji, nie są związane z bezpośrednim kontekstem typu funkcji i mogą spowodować, że program będzie źle sformułowany ([odliczenie temp.]). - uwaga końcowa] [Przykład:

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

 - przykład końca]

Ze względu na implementację std::is_invocable, która ma zastosowanie decltypedo (nieocenionego) wyrażenia wywołania wywoływalnej specjalizacji (w celu znalezienia typu zwracanego), dla specjalizacji wyzwalane jest potrącenie typu zwracanego, co wymaga wystąpienia specjalizacji, co skutkuje w tym przypadku, jak podkreślono w powyższej (nienormatywnej) uwadze, program jest źle sformułowany.