Ich versuche, einen Wrapper bereitzustellen, um std::invoke
den Funktionstyp auch dann abzuleiten, wenn die Funktion überlastet ist.
(Ich habe gestern eine verwandte Frage für die Variadic- und Methodenzeigerversion gestellt).
Wenn die Funktion ein Argument hat, funktioniert dieser Code (C ++ 17) unter normalen Überlastbedingungen wie erwartet:
#include <functional>
template <typename ReturnType, typename ... Args>
using FunctionType = ReturnType (*)(Args...);
template <typename S, typename T>
auto Invoke (FunctionType<S, T> func, T arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, T&> func, T & arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, const T&> func, const T & arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, T&&> func, T && arg)
{
return std::invoke(func, std::move(arg));
}
Das Reduzieren des Code-Aufblähens ist offensichtlich für mehr Eingabeargumente erforderlich, aber das ist ein separates Problem.
Wenn der Benutzer Überladungen hat, die sich nur durch const / reference unterscheiden, wie folgt:
#include <iostream>
void Foo (int &)
{
std::cout << "(int &)" << std::endl;
}
void Foo (const int &)
{
std::cout << "(const int &)" << std::endl;
}
void Foo (int &&)
{
std::cout << "(int &&)" << std::endl;
}
int main()
{
int num;
Foo(num);
Invoke(&Foo, num);
std::cout << std::endl;
Foo(0);
Invoke(&Foo, 0);
}
Dann Invoke
leitet daraus die Funktion falsch mit g ++ Ausgang:
(int &)
(const int &)(int &&)
(const int &)
Und clang ++:
(int &)
(const int &)(int &&)
(int &&)
(Danke an geza für den Hinweis, dass die Ergebnisse von clang unterschiedlich waren).
Hat Invoke
also undefiniertes Verhalten.
Ich vermute, dass Metaprogrammierung der Weg wäre, um dieses Problem anzugehen. Ist es unabhängig davon möglich, den Typabzug vor Ort korrekt zu handhaben Invoke
?
(int &&)
zweimal für den zweiten Fall gedruckt .S
Argumentenabzug zu tun haben . Versuchen Sie, dieconst T &
Version von zu kommentierenInvoke
und den Fehler zu bemerken. Auch wenn das Argument explizit (Invoke<void>(&Foo, num)
) angegeben wird, wird die richtige Version aufgerufen.Invoke
, kann er sie sowohl mit der Konstante als auch mit der Nicht-Konstante instanziierenFoo
. Und es wird nicht überprüft, ob der Rückgabetyp (S
) für beide gleich ist, sodass nicht abgeleitet werden kannS
. Daher wird diese Vorlage ignoriert. Während der InstanziierungInvoke
kann die Konstante nur mit der Konstante ausgeführt werdenFoo
, sodassS
in diesem Fall abgeleitet werden kann. Daher verwendet der Compiler diese Vorlage.Antworten:
Theorie
Bei jeder Funktionsvorlage
Invoke
wird bei der Ableitung des Vorlagenarguments (die erfolgreich sein muss, damit die Überlastungsauflösung berücksichtigt wird) jeweilsFoo
geprüft, ob für den einen Funktionsparameter (func
) so viele Vorlagenparameter (hier zwei) abgeleitet werden können . Der Gesamtabzug kann nur erfolgreich sein, wenn genau einerFoo
übereinstimmt (da sonst kein Abzug möglich istS
). (Dies wurde mehr oder weniger in den Kommentaren angegeben.)Der erste ("nach Wert")
Invoke
überlebt nie: Er kann aus jedem derFoo
s ableiten . In ähnlicher Weiseconst
akzeptiert die zweite ("Nichtreferenz ") Überlastung die ersten beidenFoo
s. Beachten Sie, dass diese unabhängig vom anderen Argument fürInvoke
(forarg
) gelten!Die dritte (
const T&
) Überlastung wählt die entsprechendeFoo
Überlastung aus und leitetT
= abint
; Der letzte macht dasselbe mit der letzten Überladung (woT&&
es sich um eine normale r-Wert-Referenz handelt) und lehnt daher l-Wert-Argumente trotz seiner universellen Referenz ab (die in diesem FallT
alsint&
(oderconst int&
) ableitet und mitfunc
dem Abzug in Konflikt steht ).Compiler
Wenn das Argument für
arg
ein r-Wert ist (und wie üblich nicht const ist), können beide plausiblenInvoke
Überladungen erfolgreich abgeleitet werden, und dieT&&
Überladung sollte gewinnen (da sie einen r-Wert-Verweis an einen r- Wert bindet ).Für den Fall aus den Kommentaren:
Es erfolgt kein Abzug von,
&Bar
da es sich um eine Funktionsvorlage handelt, und wird daher in jedem FallT
erfolgreich (asint
) abgeleitet . Dann Abzug geschieht wieder für jeden Fall die identifizierenBar
Spezialisierung (falls vorhanden) zu verwenden, herzuleiten ,U
wie scheitern ,int&
,const int&
, undint&
jeweils. Dieint&
Fälle sind identisch und eindeutig besser, daher ist der Anruf nicht eindeutig.Also ist Clang genau hier. (Aber hier gibt es kein "undefiniertes Verhalten".)
Lösung
Ich habe keine allgemeine Antwort für Sie; Da bestimmte Parametertypen mehrere Wertekategorie / Konstanten-Qualifizierungspaare akzeptieren können, ist es in all diesen Fällen nicht einfach, die Überlastungsauflösung korrekt zu emulieren. Es gab Vorschläge, Überlastungssätze auf die eine oder andere Weise zu korrigieren. Sie könnten eine der aktuellen Techniken in dieser Richtung in Betracht ziehen (wie ein generisches Lambda pro Zielfunktionsname).
quelle