dlaczego dedukcja typu zwracanego nie może obsługiwać SFINAE z std :: is_invocable_v
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_v
i 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 + 1
są ź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_v
tworzony jest egzemplarz, T
jest 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
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 decltype
do (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.