Ich spüre ein geringes Missverständnis in Ihrem Verständnis des Anwendungsfalls Sie konfrontiert sind.
Zunächst einmal ist dies eine Funktionsvorlage:
struct A
{
template <typename... Args>
void f(Args... args)
{
}
};
Und dies ist keine Funktion Vorlage:
template <typename... Args>
struct A
{
void f(Args... args)
{
}
};
Im ersteren Definition (mit einer Funktion Vorlage) die Art Argumente Abzug stattfinden. Im letzteren Fall gibt es keinen Typabzug.
Sie verwenden keine Funktionsvorlage. Sie verwenden eine Nicht-Template-Member-Funktion aus einer Klassenvorlage, und für diese bestimmte Member-Funktion ist ihre Signatur festgelegt.
Durch Ihre trap
Klasse definiert, wie unten:
template <typename T, T t>
struct trap;
template <typename R, typename... Args, R(Base::*t)(Args...)>
struct trap<R(Base::*)(Args...), t>
{
static R call(Args... args);
};
und unter Hinweis auf seine Funktionselement wie unten:
&trap<decltype(&Base::target), &Base::target>::call;
Sie mit einem Zeiger auf einen statischen Nicht-Template call
Funktion am Ende mit eine feste Signatur, identisch mit der Signatur der target
-Funktion.
Nun dient diese call
Funktion als ein intermediate Invoker. Sie werden die call
Funktion anrufen, und diese Funktion ruft die target
Member-Funktion, seine eigenen Argumente zu übergeben target
‚s Parameter zu initialisieren, sagen:
template <typename R, typename... Args, R(Base::*t)(Args...)>
struct trap<R(Base::*)(Args...), t>
{
static R call(Args... args)
{
return (get_base()->*t)(args...);
}
};
die target
Funktion Angenommen, die trap
Klassenvorlage verwendet zu instanziiert ist wie folgt definiert:
struct Base
{
int target(Noisy& a, Noisy b);
};
Durch Instanziierung die trap
Klasse, die Sie mit der folgenden call
Funktion am Ende:
// what the compiler *sees*
static int call(Noisy& a, Noisy b)
{
return get_base()->target(a, b);
}
Glücklicherweise a
durch Bezugnahme geben wird, wird es nur durch dieselbe Art von Referenz in der target
‚s-Parameter weitergeleitet und gebunden. Leider ist dies nicht gilt für die b
Objekt - egal ob die Noisy
Klasse beweglich ist oder nicht, werden Sie mehrere Kopien der b
Instanz zu machen, denn das ist ein Wert übergeben wird:
DEMO 1
Dies ist etwas ineffizient: Sie mindestens eine Kopie-Konstruktoraufruf gespeichert haben könnte, sie in einen Zug-Konstruktoraufruf drehen, wenn Sie nur die b
Instanz in ein xValue verwandeln konnte :
Jetzt würde es stattdessen einen Move Constructor für den zweiten Parameter aufrufen.
So weit so gut, aber das wurde manuell gemacht (std::move
hinzugefügt in dem Wissen, dass es sicher ist, die Bewegung Semantik anzuwenden).Nun ist die Frage, wie könnte die gleiche Funktionalität angewendet werden, wenn auf einem Parameter Pack Betrieb:
return get_base()->target(std::move(args)...); // WRONG!
Sie nicht std::move
Aufruf an jeden und jedes Argument innerhalb des Parameters Pack anwenden können. Dies würde wahrscheinlich Compiler-Fehler verursachen, wenn sie auf alle Argumente gleichermaßen angewendet werden.
DEMO 2
Zum Glück, obwohl Args...
ist kein Forwarding-Referenz, die std::forward
Helferfunktion stattdessen verwendet werden können. Das heißt, je nachdem, was der <T>
Typ ist in std::forward<T>
(eine L-Wert-Referenz oder eine Nicht-L-Wert-Referenz) die std::forward
verhält sich anders:
für lvalue Referenzen (beispielsweise wenn T
ist Noisy&
): Der Wert Die Kategorie des Ausdrucks bleibt ein lvalue (dh Noisy&
).
für Nicht-L-Wert-Referenzen (z.B. wenn T
Noisy&&
oder ein ebenes Noisy
ist): der Wert der Kategorie des Ausdrucks ein xValue wird (d.h. Noisy&&
).
Nachdem das gesagt ist, durch Definieren der target
Funktion wie unten:
static R call(Args... args)
{
return (get_base()->*t)(std::forward<Args>(args)...);
}
Sie am Ende mit:
static int call(Noisy& a, Noisy b)
{
// what the compiler *sees*
return get_base()->target(std::forward<Noisy&>(a), std::forward<Noisy>(b));
}
den Wert Kategorie des Ausdrucks drehen b
in eine xValue Beteiligung von b
, die Noisy&&
ist. Dadurch kann der Compiler den Verschiebungskonstruktor auswählen, um den zweiten Parameter der target
-Funktion zu initialisieren, wobei a
intakt bleibt.
DEMO 3(vergleiche die Ausgabe mit DEMO 1)
Im Grunde ist das, was die für std::forward
ist. Normalerweise wird std::forward
mit einer Weiterleitungsreferenz verwendet, wobei T
den Typ hält, der nach den Regeln des Typenabzugs für Weiterleitungsreferenzen abgeleitet wird. Beachten Sie, dass es immer von Ihnen verlangt, den <T>
Teil explizit zu übergeben, da es ein anderes Verhalten abhängig von diesem Typ anwendet (unabhängig von der Wertkategorie seines Arguments). Ohne das Template-Argument <T>
des expliziten Typs würde std::forward
immer Lvalue-Referenzen für Argumente ableiten, auf die durch ihre Namen verwiesen wird (wie beim Erweitern des Parameter-Packs).
Nun wollten Sie zusätzlich konvertieren Sie einige der Argumente von einem Typ zu einem anderen, während alle anderen weiterleiten.Wenn Sie nicht mit std::forward
ing Argumente aus dem Parameterpaket über den Trick ist es egal, und es ist in Ordnung, eine Kopie-Konstruktor immer anrufen, dann Ihre Version ist OK:
template <typename T> // transparent function
T&& process(T&& t) {
return std::forward<T>(t);
}
Bar process(Foo x) { // overload for specific type of arguments
return Bar{x};
}
//...
get_base()->target(process(args)...);
DEMO 4
jedoch wenn Sie die Kopie dieses Noisy
Argument in der Demo vermeiden wollen, müssen Sie irgendwie std::forward
Anruf mit dem process
Anruf, und Durchlauf über die Args
Typen kombinieren, so dass std::forward
richtiges Verhalten (t gelten könnte in xvalues gehen oder nichts tun). Ich habe Ihnen nur ein einfaches Beispiel dafür gegeben, wie dies implementiert werden könnte:
template <typename T, typename U>
T&& process(U&& u) {
return std::forward<T>(std::forward<U>(u));
}
template <typename T>
Bar process(Foo x) {
return Bar{x};
}
//...
get_base()->target(process<Args>(args)...);
Aber das ist nur eine der Optionen. Es kann vereinfacht werden, neu geschrieben oder neu angeordnet werden, so dass std::forward
aufgerufen wird, bevor Sie die process
Funktion (Ihre Version) nennen:
get_base()->target(process(std::forward<Args>(args))...);
DEMO 5
Und es (die Ausgabe mit DEMO 4 vergleichen) wird auch gut funktionieren (das heißt, mit Ihrer Version). Der Punkt ist, die zusätzliche std::forward
ist nur, um Ihren Code ein wenig zu optimieren, und die provided idea war nur eine der möglichen Implementierungen dieser Funktionalität (wie Sie sehen können, bringt es den gleichen Effekt).
In diesen Beispielen fehlt der Kontext, in dem die Prozessfunktion aufgerufen wird - immer mit einem expliziten Typ-Template-Argument (zumindest war das die Idee, Nicht-Lvalue-Referenzen in xvalues zu verwandeln). Sie können mit Ihrer Version kommen, aber es wird immer copy ctor beim Initialisieren von Parametern der Zielfunktion aufrufen –
Ich stimme zu, dass Version 1 irgendwie "schmutzig" ist, weil es eine Überladung von nicht-gestützten Funktionen über eine Vorlage ist und sie unterscheiden sich nur nach Rückgabetyp. Aber für Version 2 kann ich keinen Sinn erkennen. Sie müssten den ersten Template-Parameter ohnehin immer angeben, da der Compiler ihn in einem Verwendungskontext nicht ableiten kann. –
@PiotrS., Ich habe bearbeitet, um einen Kontext bereitzustellen (ich denke, es entspricht meinem tatsächlichen Anwendungsfall). Dank Ihrer unschätzbaren Hilfe in den letzten Tagen habe ich jetzt eine funktionierende Lösung ([hier] (http://stackoverflow.com/q/27866483/435129)). Dies ist die letzte Komponente, an der ich kaue, weil ich nicht gerne Code einbeziehe, den ich nicht verstehe. –