Sie müssen das Weiterleitungsproblem verstehen. Sie können das gesamte Problem im Detail lesen , aber ich werde es zusammenfassen.
Grundsätzlich E(a, b, ... , c)
wollen wir angesichts des Ausdrucks , dass der Ausdruck f(a, b, ... , c)
äquivalent ist. In C ++ 03 ist dies nicht möglich. Es gibt viele Versuche, aber alle scheitern in gewisser Hinsicht.
Am einfachsten ist es, eine Wertreferenz zu verwenden:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
E(a, b, c);
}
Dies behandelt jedoch keine temporären Werte : f(1, 2, 3);
, da diese nicht an eine Wertreferenz gebunden werden können.
Der nächste Versuch könnte sein:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c);
}
Was das obige Problem behebt, aber Flops flippt. Es ist jetzt nicht möglich E
, nicht konstante Argumente zu haben:
int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
Der dritte Versuch akzeptiert konst Verweise, aber dann const_cast
ist der const
weg:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}
Dies akzeptiert alle Werte, kann alle Werte weitergeben, führt jedoch möglicherweise zu undefiniertem Verhalten:
const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!
Eine endgültige Lösung handhabt alles richtig ... auf Kosten der Unwartbarkeit. Sie bieten Überladungen von f
mit allen Kombinationen von const und non-const:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);
N Argumente erfordern 2 N Kombinationen, ein Albtraum. Wir möchten dies automatisch tun.
(Dies ist effektiv das, was der Compiler in C ++ 11 für uns tun soll.)
In C ++ 11 haben wir die Möglichkeit, dies zu beheben. Eine Lösung ändert die Regeln für den Vorlagenabzug für vorhandene Typen. Dadurch wird jedoch möglicherweise viel Code beschädigt. Also müssen wir einen anderen Weg finden.
Die Lösung besteht darin, stattdessen die neu hinzugefügten rvalue-Referenzen zu verwenden . Wir können neue Regeln einführen, wenn wir rWert-Referenztypen ableiten und jedes gewünschte Ergebnis erzielen. Schließlich können wir jetzt unmöglich Code brechen.
Wenn eine Referenz auf eine Referenz angegeben wird (Hinweisreferenz ist ein umfassender Begriff, der sowohl T&
als als auch bedeutet T&&
), verwenden wir die folgende Regel, um den resultierenden Typ herauszufinden:
"[gegeben] ein Typ TR, der eine Referenz auf einen Typ T ist, ein Versuch, den Typ" lWertreferenz auf Lebenslauf TR "zu erstellen, erzeugt den Typ" lWertreferenz auf T ", während ein Versuch, den Typ" rWertreferenz auf T "zu erstellen cv TR "erstellt den Typ TR."
Oder in tabellarischer Form:
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
Als nächstes mit Abzug von Vorlagenargumenten: Wenn ein Argument ein l-Wert A ist, liefern wir dem Vorlagenargument einen l-Wert-Verweis auf A. Andernfalls leiten wir normal ab. Dies ergibt sogenannte universelle Referenzen (der Begriff Weiterleitungsreferenz ist jetzt die offizielle).
Warum ist das nützlich? Da wir zusammen die Möglichkeit behalten, die Wertekategorie eines Typs zu verfolgen: Wenn es sich um einen l-Wert handelt, haben wir einen l-Wert-Referenzparameter, andernfalls haben wir einen r-Wert-Referenzparameter.
In Code:
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
Als letztes wird die Wertekategorie der Variablen "weitergeleitet". Denken Sie daran, dass der Parameter innerhalb der Funktion als Wert an alles übergeben werden kann:
void foo(int&);
template <typename T>
void deduce(T&& x)
{
foo(x); // fine, foo can refer to x
}
deduce(1); // okay, foo operates on x which has a value of 1
Das ist nicht gut. E muss die gleiche Art von Wertkategorie bekommen, die wir haben! Die Lösung lautet:
static_cast<T&&>(x);
Was macht das? deduce
Stellen Sie sich vor, wir befinden uns innerhalb der Funktion und haben einen l-Wert erhalten. Dies bedeutet , dass a ein Ziel T
ist A&
, und daher ist der Zieltyp für die statische Umwandlung A& &&
oder nur A&
. Da dies x
bereits ein ist A&
, tun wir nichts und bleiben mit einer Wertreferenz zurück.
Wenn uns ein r-Wert übergeben wurde, T
ist der Zieltyp A
für die statische Umwandlung A&&
. Die Umwandlung führt zu einem r-Wert-Ausdruck, der nicht mehr an eine l-Wert-Referenz übergeben werden kann . Wir haben die Wertekategorie des Parameters beibehalten.
Wenn wir diese zusammenfügen, erhalten wir eine "perfekte Weiterleitung":
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
Wenn f
ein l E
-Wert empfangen wird, erhält er einen l-Wert. Wenn f
ein rWert empfangen wird, E
erhält er einen rWert. Perfekt.
Und natürlich wollen wir das Hässliche loswerden. static_cast<T&&>
ist kryptisch und seltsam zu erinnern; Lassen Sie uns stattdessen eine Utility-Funktion namens forward
aufrufen, die dasselbe tut:
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
f
Funktion und kein Ausdruck?