2014-01-20 20 views
18

Wenn ein abgeleitetes Typ als r-Referenzwert vorbei erhalte ich universelle Funktionalität Referenz und kann perfekt Weiterleitung wie folgt archieve:Erfassung durch universelle Referenz

template <typename T> 
void func(T&& t) { 
    other_func(std::forward<T>(t)); 
} 

... aufgrund der Art und Weise T abgeleitet wird, und dem Standard des Regeln zum Kollabieren von Referenzen

Betrachten wir nun other_func ein Funktionsobjekt nimmt

template <typename T> 
void func(T&& t) { 
    other_func([](int v) { return t + v; }); // I chose addition for example purposes 
} 

Jetzt offensichtlich wird dies aufgrund t nicht erfasst wird, nicht kompilieren. Meine Frage ist: Wie erfasse ich es, so dass der erfasste Wert ist, was auch immer T abgeleitet wird?

Ist dies mit den neuen generischen Lambda-Captures möglich? Und wenn ... wie?

[t = std::forward<T>(t)] ? 

ich wirklich noch nicht, die Mechanik der neuen Capture initializers bekommen ...

+0

Schnell Anmerkung: der derzeit anerkannten Name Bezug genommen wird Forwarding, nicht universelle Referenz –

Antwort

5

Sie können "Capture durch Universal-Referenz" in C++ 11, da die Art der Template-Parameter T auf die Lambda-Funktion verfügbar ist (hideous live code example at Coliru):

template <typename T> 
void func(T&& t) { 
    other_func([&t](int v) { 
    return std::forward<T>(t) + v; 
    }); 
} 
+0

Nun, das ist interessant - dachte, der Typ von t innerhalb des Lambda wäre zu einer l-Wert-Referenz kollabiert. Noch eine Anmerkung: Wie lautet der Name der Referenzspezifizierer, die Sie nach der Operatordeklaration schreiben? Wusste nicht, dass diese existieren –

+0

@RichardVock Das sind [ref-Qualifier] (http://stackoverflow.com/questions/17521238/what-are-rvalue-references-for-this-for). Ähnlich wie "const" -Qualifikatoren in Elementfunktionen beschränken sie die Objekte, auf denen die Funktion aufgerufen werden kann. Zum Beispiel wird 'void foo() &&' aufgerufen, wenn Sie 'foo' für ein rvalue-Objekt aufrufen. – Casey

+4

Ein mögliches Problem: wenn 'other_func' das Lambda speichert und es länger als 'func's Body Scope dauert, ist der Aufruf von Lambda UB, da die erfasste Variable' t' den Gültigkeitsbereich verlassen hat. – Yakk

6

Okay, lassen Sie uns dies versuchen. Leider habe ich keinen Compiler, der diese Funktion unterstützt, also vergib mir, wenn ich die Dinge falsch interpretiere.

Der Vorschlag hierzu lautet N3648.

Der interessante Teil ist, dass die Art der Variablen in den init-Capture abgeleitet wird, als ob auto mit:

Die Art dieses Elements entspricht den Typen einer hypothetischen Variablendeklaration des Formulars "automatische Init-Aufnahme;" [...].

So ist die Frage, was man von einem Capture-Liste erhalten, ist [c = std::forward<T>(t)] entspricht dem, was man von einer Erklärung auto c = std::forward<T>(t) bekommen.

Der abgeleitete Typ ist hier std::remove_reference<T>::type (Referenzqualifikatoren werden von auto gelöscht), so dass Sie hier immer mit einem neuen Wert enden. Wenn t eine Rvalue-Referenz ist, werden Sie den neuen Wert move-construct, andernfalls werden Sie (aufgrund des Rückgabewerts std::forward) kopieren.

Die gute Sache ist, dass dieser neue Wert im Besitz des Lambda ist. Also, egal was t Sie zunächst übergeben, ist es sicher std::move von der erfassten c. Auch wenn Sie den Typ der ursprünglichen t nicht mehr kennen, haben Sie auf dem Weg noch nichts verloren.

+0

Ich denke, Sie benötigen einen Wrapper-Typ, um die perfekte Weiterleitung zu unterstützen. Ein Rvalue-Referenzelement würde das Kopieren nicht zulassen, und ein Verschiebungskonstruktor muss nicht für ein Lambda verfügbar sein. – dyp

+0

@dyp Ja, ein Wrapper-Typ würde funktionieren. Es wäre aber auch ziemlich hässlich ('std :: auto_ptr_ref', irgendjemand?). Das Interessante ist, ich glaube nicht, dass wir einen substanziellen Verlust erleiden, indem wir die perfekte Weiterleitung hier opfern: Wir kommen nur mit Zügen davon, wenn wir sonst einen Wert und eine Kopie einreichen. – ComicSansMS

+1

Das stimmt, aber Bewegungen können auch teuer sein, z.B. wenn ein Mitglied nur kopiert werden kann (Legacy-Datentyp, Array, ..). Ich dachte an etwas wie 'Vorlage Wrapper {T m; }; Vorlage automatisches Umbrechen (T && p) -> wrapper {return {std :: vorwärts (p)}; } 'und dann' [t = wrap (std :: forward (t))] ') – dyp

2

Um das gewünschte Verhalten zu erreichen mit Erfassung durch Bezugnahme eingeschlossen, keine generische Lambda Erfassung von 14 ++ C erforderlich ist (aber wie immer mit Fänger Bezug genommen wird, ist Vorsicht geboten keinen baumelnden Verweis zu erstellen):

template <typename T> 
void func(T&& t) { 
    other_func([&t](int v) { return std::forward<T>(t) + v; }); 
} 

im Gegenteil, wenn die Entscheidung gemacht zu verwenden c Apture nach Wert, sollte das Lambda als wandelbar markiert werden, die wirksam in Bewegung zu ermöglichen (weil ein const Qualifier implizit lambdas hinzugefügt wird):

template <typename T> 
void func(T&& t) { 
    other_func([t = std::forward<T>(t)](int v) mutable { return std::move(t) + v; }); 
} 
+0

Das Lambda im obigen Beispiel wird nicht durch Wert erfasst - daher ist Mutable dort nicht notwendig (in der Tat ein guter Weg, um mutable und C++ 14 generische Lambdasonden zu vermeiden) – user1115339

+0

Oh, das habe ich verpasst. Du hast Recht. – dyp