Warum der Abzug vom Rückgabetyp SFINAE mit std :: is_invocable_v nicht unterstützen kann
Es gibt einige Funktoren mit Rückzugsartabzug , einschließlich Lambda-Ausdrücken .
constexpr auto a = [](auto&& x){ return x + 1; };
struct X{
template<typename T>
auto operator()(T&& x){
return x + 1;
}
};
und dann habe ich 2 Funktionen, um zu überprüfen, ob die Argumente auf diese Funktoren angewendet werden können, von std::is_invocable_v
und 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;
}
Schließlich benutze ich foo<X>(0)
oder foo<decltype(a)>(0)
, und es funktioniert gut und sagt "Ja", weil die Prüfung bestanden ist. aber wenn ich foo<X>((void*)0)
oder benutze foo<decltype(a)>((void*)0)
, erhalte ich einen Kompilierungsfehler anstelle von "nein".
Die Substitution erfolgt in
1. alle Typen in dem verwendeten Funktionstyp (welcher Rückgabetyp und die Typen aller Parameter )
...
Es sieht so aus, als würden diese Funktoren Argumente jeglicher Art akzeptieren und dann einen Fehler auslösen, wenn sie x + 1
schlecht geformt sind. Der Rückgabetyp von operator()
wird jedoch von abgeleitet x + 1
, was bedeutet, dass er vom Typ des Arguments abhängt T
. wenn das std::is_invocable_v
instanziert wird, T
wird substituiert durch void*
, und dann die Signatur operator()
hat einen ungültigen Rückgabetyp . ist das ein Ersatzfehler ?
Um diese Frage zu klären, definiere ich diese 2 Funktoren:
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;
}
};
Wenn der Rückgabetyp ist
decltype(auto)
, entspricht der Rückgabetyp dem, was erhalten würde, wenn der in der return-Anweisung verwendete Ausdruck eingeschlossen wäredecltype
.
aber warum foo<Z>((void*)0)
sagt "nein" aber foo<Y>((void*)0)
führt zu einem fehler?
Antworten
Dies ist [dcl.spec.auto] / 11 .
Abgeleitete Rückgabetypen sind nicht SFINAE-freundlich, da die Abfrage des Rückgabetyps eines Vorlagenaufrufs (Funktionsvorlage / generisches Lambda / Funktor mit Vorlage operator()(...)
), der den Rückzugstypabzug nutzt, die Instanziierung der jeweiligen Spezialisierung des Aufrufbaren als Definition erfordert der Spezialisierung wird benötigt, um den Rückgabetyp abzuleiten:
[dcl.spec.auto] / 11 Der Abzug des Rückgabetyps für eine Funktionsvorlage mit einem Platzhalter im deklarierten Typ erfolgt, wenn die Definition instanziiert wird, auch wenn der Funktionskörper eine return-Anweisung mit einem nicht typabhängigen Operanden enthält. [ Hinweis: Daher führt jede Verwendung einer Spezialisierung der Funktionsvorlage zu einer impliziten Instanziierung. Alle Fehler, die sich aus dieser Instanziierung ergeben, stehen nicht im unmittelbaren Kontext des Funktionstyps und können dazu führen, dass das Programm fehlerhaft ist ([temp.deduct]). - Endnote] [Beispiel:
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
- Beispiel beenden]
Aufgrund der Implementierung von std::is_invocable
, die für decltype
den (nicht bewerteten) Ausdruck des Aufrufs der aufrufbaren Spezialisierung (um den Rückgabetyp zu finden) gilt, wird für die Spezialisierung ein Rückzugstypabzug ausgelöst, der die Instanziierung der Spezialisierung erfordert, was zu führt In diesem Fall ist das Programm, wie in der (nicht normativen) Anmerkung oben hervorgehoben, schlecht ausgebildet.