2010-10-09 7 views
5

Was ist der beste Weg, um eine Rvalue-Referenz entweder an ein gegebenes Objekt oder eine temporäre Kopie davon zu binden?Stil beim Binden eines Verweises auf Objekt oder Dummy

A &&var_or_dummy = modify? static_cast<A&&>(my_A) 
         : static_cast<A&&>(static_cast<A>(my_A)); 

(Dieser Code funktioniert nicht auf meinem letzten GCC 4.6 ... Ich erinnere mich, es vor der Arbeit, aber jetzt ist es gibt immer eine Kopie.)

In der ersten Zeile, die static_cast verwandelt my_A von einer Lvalue zu einem xvalue. (C++ 0x §5.2.9/1-3) Die innere static_cast in der zweiten Zeile führt Lvalue-to-rvalue-Konvertierung, und die äußere erhält einen xvalue von diesem prvalue.

Dies scheint unterstützt zu werden, da die benannte Referenz bedingt an die temporäre nach §12.2/5 gebunden ist. Derselbe Trick funktioniert auf die gleiche Weise in C++ 03 mit einer const Referenz.

Ich kann auch die gleiche Sache weniger verbosely schreiben:

A &&var_or_dummy = modify? std::move(my_A) 
         : static_cast<A&&>(A(my_A)); 

Jetzt ist es viel kürzer. Die erste Abkürzung ist fraglich: move soll signalisieren, dass etwas mit dem Objekt passiert, nicht nur ein lvalue-to-xvalue-to-lvalue shuffle. Verwirrenderweise kann move nicht nach : verwendet werden, da der Funktionsaufruf die Bindung zwischen temporär und Referenz unterbrechen würde. Die Syntax A(my_A) ist vielleicht klarer als die static_cast, aber es ist technisch äquivalent zu einem C-Style-Cast.

Ich kann auch den ganzen Weg gehen und schreiben Sie es vollständig in C-Casts:

A &&var_or_dummy = modify? (A&&)(my_A) : (A&&)(A(my_A)); 

Nach allem, wenn dies ein Idiom sein wird, ist es zweckmäßig sein muss und static_cast ist nicht schützt mich wirklich vor allem - die eigentliche Gefahr besteht darin, nicht direkt an my_A im true Fall zu binden.

Auf der anderen Seite wird dies leicht durch den Typnamen beherrscht, der dreimal wiederholt wird. Wenn A durch eine große, hässliche Template-ID ersetzt würde, würde ich wirklich eine echte Abkürzung wollen.

(Beachten Sie, dass V trotz erscheinen fünf Mal nur einmal ausgewertet wird :)

#define VAR_OR_DUMMY(C, V) ((C)? \ 
    static_cast< typename std::remove_reference< decltype(V) >::type && >(V) \ 
: static_cast< typename std::remove_reference< decltype(V) >::type && > ( \ 
    static_cast< typename std::remove_reference< decltype(V) >::type >(V))) 

hackish als Makros sind, denke ich, dass die beste Alternative des Bündels ist. Es ist ein bisschen gefährlich, weil es einen xvalue zurückgibt, also sollte es nicht außerhalb der Referenzinitialisierung verwendet werden.

Da muss etwas sein, an das ich nicht gedacht habe ... Vorschläge?

+0

Ohne ein Makro, ich denke, Sie können eine weitere der Erwähnungen des Typs 'A' mit Hilfe von' auto && var_or_dummy = ... 'ausschneiden. Nicht, dass das viel besser wäre ... Zu meiner Erbauung: 'VAR_OR_DUMMY' kann nicht als Funktionsvorlage implementiert werden, da das Temporäre direkt an die Rvalue-Referenz gebunden werden muss und eine Rvalue-Referenz von einer Funktion nicht richtig funktioniert ? –

+0

@James: Kann ich 'auto' irgendwie anstelle von' remove_reference :: type' verwenden? Ja, das ist mein Denken. Ich habe versucht, dass es auch einen Lvalue-Bezug zurückgibt (siehe Edit-Verlauf), aber oh, keine Funktionen erlaubt. – Potatoswatter

+0

Nein, ich glaube nicht, dass "auto" anstelle der 'remove_reference :: type' verwendet werden kann (obwohl ich beim Nachschlagen festgestellt habe, dass dies gilt:' auto p = new auto (1); ' ... 'p' hat den Typ' int * '). –

Antwort

2

einfach dieses ganze Durcheinander mit einem zusätzlichen Funktionsaufruf vermeiden:

void f(bool modify, A &obj) { 
    return [&](A &&obj) { 
    real(); 
    work(); 
    }(modify ? std::move(obj) : std::move(A(obj))); 
} 

Statt:

void f(bool modify, A &obj) { 
    A &&var_or_dummy = /* ??? */; 
    real(); 
    work(); 
} 

Es ist lambdas, lambdas, everywhere!

+0

Sind Sie sicher, dass das Ergebnis des bedingten Operators in diesem Fall jemals auf das ursprüngliche Objekt verweisen kann? Der zweite Operand ist ein xvalue und der dritte Operand ist ein prvalue. Und selbst wenn Sie ein std :: move() um A (obj) hinzufügen, wird dies nicht mit GCC funktionieren, da GCC fehlerhaft zu sein scheint. die Bedingungsoperator- und xvalue-Operanden. Ich habe nicht den letzten GCC-Build. Haben Sie diesen Code zufällig getestet? – sellibitze

+0

@sellibitze: Dies ist die kanonische Methode, xvalues ​​zu verwenden. Derselbe Fehler mit '?:' Und xvalues ​​beeinflusst meinen Code ... Ich würde erwarten, dass das in Kürze behoben wird. (Und es hat in der Vergangenheit funktioniert.) – Potatoswatter

+1

+1, das ist auch die kanonische Art, Lambda-Ausdrücke zu verwenden. Dies würde dazu tendieren, die funktionale Reinheit von C++ 0x zu testen ... wie viel Unterschied macht es wirklich, dass dieser Block seinen eigenen Stapelrahmen hat? – Potatoswatter

2

Ich sehe zwei Probleme mit Ihrem Ansatz.

Sie verlassen sich auf das Verhalten

int i = 0; 
int& j = true?  i :  i; 
int&& k = true? move(i) : move(i); 
assert(&i == &j); // OK, Guaranteed since C++98 
assert(&i == &k); // Does this hold as well? 

Die aktuelle Standardentwurf N3126 5 enthält.16/4:

Wenn die zweiten und dritten Operanden [zum Bedingungsoperator] glvalues ​​der gleichen Wertkategorie und den gleichen Typ sind, ist das Ergebnis dieser Art und Wertkategorie

was mich denken lässt, dass die obigen zwei Behauptungen gelten sollten. Aber mit GCC 4.5.1 schlägt der zweite fehl. Ich glaube, das ist ein GCC-Bug.

Darüber hinaus setzen Sie auf den Compiler Lebensdauer des temporären Objekt zu erweitern y bezieht sich auf das folgende Beispiel:

A func(); 

A&& x = func();     // #1 
A&& y = static_cast<A&&>(func()); // #2 

x wird kein baumelnden Referenz sein, aber ich bin nicht so sicher über y. Ich denke, die Regel über die Verlängerung der Lebensdauer von Provisorien soll nur in Fällen gelten, in denen die Initialisierungsausdrücke pure rvalues ​​sind. Zumindest würde dies die Implementierung erheblich vereinfachen. Auch GCC scheint mir in diesem Punkt zuzustimmen. GCC verlängert die Lebensdauer des temporären A-Objekts im zweiten Fall nicht. Dies wäre ein Dangling-Referenzproblem in Ihrem Ansatz .

Update: Laut 12.2/5 sollen die Lebensdauern der temporären Objekte in beiden Fällen, # 1 und # 2, verlängert werden. Keiner der Aufzählungspunkte in der Liste der Ausnahmen scheint hier zu gelten. GCC scheint in dieser Hinsicht wiederum fehlerhaft zu sein.

Eine einfache Lösung für Ihr Problem wäre:

vector<A> tempcopy; 
if (!modify) tempcopy.push_back(myA); 
A& ref = modify ? myA : tempcopy.back(); 

Alternativly, Ihnen einen Schub nutzen könnten :: scoped_ptr anstelle eines Vektors.

+0

Yep ... (In Ihrem ersten Beispiel ist 'static_cast' notwendig anstelle von' move', was eine Funktion ist.) Die FCD unterscheidet nicht zwischen Lvalue-Referenzen und Rvalue-Referenzen in der Lifetime-Extension-Klausel, also bin ich mir ziemlich sicher Referenzen sind in dieser Hinsicht ähnlich. +1 für die Problemumgehung ... schade, dass C++ 0x Boost Optional nicht importiert hat, welches das beste Werkzeug für diesen Job ist. – Potatoswatter

+0

@Potatoswatter: Ich bin verwirrt. Warum denkst du, dass es im * ersten * Beispiel einen Unterschied macht, ob move oder static_cast verwendet wird? Das erste Beispiel handelt nicht von Lebenszeitproblemen. Es geht darum, ob der resultierende xvalue-Ausdruck tatsächlich auf das ursprüngliche Objekt verweist. – sellibitze

+0

@sellibitze: 12.2/5. Eine Schlüsselidee ist, dass die Zwischenausdrücke xvalues ​​sind, keine Referenzen, so dass die Formulierung "referenz is bound" ausschließlich auf benannte Objekte verweist. (Und der Rückgabewert einer Funktion, die mit dem Referenztyp deklariert wurde, was ein ausgeschlossener Fall ist.) – Potatoswatter

0

Das Problem der xvalue-Sicherheit kann etwas umgangen werden, indem eine Alternative zur Verwendung in Ausdrücken bereitgestellt wird. Die Fragen sind ganz anders, jetzt wir nicht wollen ein xValue Ergebnis tun und eine Funktion verwenden können:

template< typename T > 
T &var_or_dummy(bool modify, T &var, T &&dummy = T()) { 
    if (modify) return var; 
    else return dummy = var; 
} 

    maybe_get_result(arg, var_or_dummy(want_it, var)); 

der Typ Jetzt hat default-konstruierbar sein, und die Dummy ist immer konstruiert. Die Kopie wird bedingt ausgewertet. Ich glaube nicht, dass ich mich wirklich mit Code befassen würde, der zu viel davon gemacht hat.

Boost Optional kann ein wenig helfen; es erfordert nur CopyConstructible T:

template< typename T > 
T &var_or_dummy(bool modify, T &var, 
       boost::optional<T> &&dummy = boost::optional<T>()) { 
    if (modify) return var; 
    else return dummy = var; 
} 

Optional nützlich ist, aber es einige Überschneidungen mit C++ 0x Gewerkschaften hat. Es ist nicht zu schwer zu re-implementieren.

template< class T > 
struct optional_union { 
    bool valid; 
    union storage { 
     T obj; // union of one non-POD member simply reserves storage 

     storage() {} // uh, what could the constructor/destructor possibly do?? 
     ~storage() {} 
    } s; 

    optional_union() : valid(false) {} 
    optional_union &operator=(T const &in) { 
     new(&s.obj) T(in); // precondition: ! valid 
     valid = true; 
     return *this; 
    } 
    ~optional_union() 
     { if (valid) s.obj.~T(); } 
}; 

template< typename T > 
T &var_or_dummy(bool modify, T &var, 
       optional_union<T> &&dummy = optional_union<T>()) { 
    if (modify) return var; 
    else return (dummy = var).s.obj; 
} 

Die optional_union Klasse ist für diese Anwendung nur ausreichend ... offensichtlich kann es viel erweitert werden.

+1

Da Sie "work" bereits in eine separate Funktion für Ihr erstes Beispiel eingefügt haben, könnten Sie genauso gut "if (modify) {work (myA); } else {Eine Kopie = myA; Arbeit (Kopie); } ';-) – sellibitze

+0

@sellibitze: Das Objekt soll Code-Duplikation vermeiden. – Potatoswatter

Verwandte Themen