2010-10-09 3 views
5

Qual è il modo migliore per associare un riferimento di rvalue a un determinato oggetto o una copia temporanea di esso?nel collegamento di un riferimento a object-or-dummy

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

(Questo codice non funziona sul mio recente GCC 4.6 ... Ricordo che funziona prima, ma ora si ritorna sempre una copia.)

Nella prima riga, il static_cast trasforma my_A da un lvalue su un valore x. (C++ 0x §5.2.9/1-3) Il numero interno static_cast sulla seconda riga esegue la conversione da lvalue a rvalue e quello esterno ottiene un valore x da questo valore.

Questo sembra essere supportato perché il riferimento nominato è condizionalmente vincolato al temporaneo per §12.2/5. Lo stesso trucco funziona allo stesso modo in C++ 03 con un riferimento const.

posso anche scrivere la stessa cosa meno verboso:

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

Ora è molto più breve. La prima abbreviazione è discutibile: si suppone che il segnale move segnali che qualcosa sta accadendo all'oggetto, non un semplice shuffle da lvalue a xvalue-to-lvalue. In modo confuso, move non può essere utilizzato dopo lo : perché la chiamata di funzione interromperebbe l'associazione temporanea al riferimento. La sintassi A(my_A) è forse più chiara dello static_cast, ma è tecnicamente equivalente a un cast in stile C.

posso anche andare fino in fondo e scrivere interamente in C-style calchi:

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

Dopo tutto, se questo sta per essere un linguaggio, deve essere conveniente, e non è static_cast mi sta davvero proteggendo da qualsiasi cosa - il vero pericolo non è riuscire a legare direttamente a my_A nel caso true.

D'altra parte, questo è facilmente dominato dal nome tipografico ripetuto tre volte. Se lo A venisse sostituito con un modello di identificazione grande e brutto, vorrei davvero una vera scorciatoia.

(Si noti che V viene valutata solo una volta pur apparendo cinque volte :)

#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))) 

hacker come macro sono, penso che sia la migliore alternativa del gruppo. È un po 'pericoloso perché restituisce un valore x, quindi non dovrebbe essere usato al di fuori dell'inizializzazione del riferimento.

Deve esserci qualcosa a cui non ho pensato ... suggerimenti?

+0

Senza una macro penso che si possa ritagliare un'altra delle menzioni di tipo 'A' usando' auto && var_or_dummy = ... '. Non che sia molto meglio ... Per la mia edificazione: 'VAR_OR_DUMMY' non può essere implementato come un modello di funzione perché il temporaneo deve essere associato direttamente al riferimento di rvalue e il ritorno di un riferimento di rvalue da una funzione non funzionerà, a destra ? –

+0

@James: Posso usare 'auto' in qualche modo invece di' remove_reference :: type'? ... Sì, questo è il mio pensiero. Ho provato a fare in modo che restituisca un riferimento lvalue (vedi la cronologia delle modifiche) ma d'oh, nessuna funzione è permessa. – Potatoswatter

+0

No, non penso che 'auto' possa essere usato al posto di' remove_reference :: type' (sebbene, osservandolo, ho scoperto che questo è valido: 'auto p = new auto (1);' ... 'p' ha tipo' int * '). –

risposta

2

Basta evitare tutto questo casino con una chiamata di funzione in più:

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

Invece di:

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

E 'lambdas, lambdas, everywhere!

+0

Sei sicuro che il risultato dell'operatore condizionale possa mai riferirsi all'oggetto originale in questo caso? Il secondo operando è un xvalue e il terzo operando è un prvalore. E anche se si aggiunge uno std :: move() attorno a A (obj), questo non funzionerà con GCC poiché GCC sembra essere bacato con w.r.t. l'operatore condizionale e gli operandi xvalue. Non ho la build GCC più recente. Hai provato questo codice per caso? – sellibitze

+0

@sellibitze: questo è il modo canonico di usare xvalue. Lo stesso bug con '?:' E xvalues ​​influenza il mio codice ... Mi aspetterei che venga risolto in poco tempo. (E ha funzionato in passato.) – Potatoswatter

+1

+1, questo è anche il modo canonico per usare le espressioni lambda. Questo tenderebbe a testare la purezza funzionale del C++ 0x ... quanta differenza fa * veramente * che questo blocco abbia il proprio stack frame? – Potatoswatter

2

Vedo due problemi con il tuo approccio.

si basano sul comportamento

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? 

L'attuale progetto di norma N3126 contiene 5.16/4:

Se il secondo e terzo operandi [all'operatore condizionale] sono glvalues ​​della stessa categoria valore e hanno lo stesso tipo, il risultato è di quel tipo e valore di categoria

il che mi fa pensare che le due affermazioni precedenti debbano valere. Ma usando GCC 4.5.1 il secondo fallisce. Credo che questo sia un bug GCC.

Inoltre, ci si affida al compilatore di estendere il tempo di vita dell'oggetto temporanea y fa riferimento al seguente esempio:

A func(); 

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

x non ci sarà un punto di riferimento penzoloni , ma io non sono così sicuro di y. Penso che la regola sull'estensione del tempo di vita dei provvisori si debba applicare solo nei casi in cui le espressioni di inizializzazione sono valori puri. Almeno, questo semplificherebbe notevolmente l'implementazione. Inoltre, GCC sembra essere d'accordo con me su questo. GCC non estende la durata dell'oggetto A temporaneo nel secondo caso. Questo sarebbe un problema di riferimento ciondolante nel tuo approccio .

Aggiornamento: Secondo 12,2/5 i tempi di vita degli oggetti temporanei dovrebbero essere esteso in entrambi i casi, # 1 e # 2. Nessuno dei punti elenco nell'elenco delle eccezioni sembra applicarsi qui. Ancora una volta, GCC sembra essere buggato in questo senso.

Una semplice soluzione per il problema potrebbe essere:

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

Alternativly, è possibile utilizzare un boost :: scoped_ptr invece di un vettore.

+0

Sì ... (Nel tuo primo esempio, 'static_cast' è necessario invece di' move' che è una funzione.) L'FCD non distingue tra i riferimenti lvalue e i riferimenti rvalue nella clausola di estensione della durata, quindi sono abbastanza sicuro i riferimenti sono simili in questo senso. +1 per la soluzione ... troppo male C++ 0x non ha importato Boost Facoltativo, che è lo strumento migliore per quel lavoro. – Potatoswatter

+0

@Potatoswatter: Sono confuso. Perché pensi che faccia qualche differenza nell'esempio * primo * se viene usato move o static_cast? Il primo esempio non riguarda i problemi della vita. Riguarda se l'espressione xvalue risultante si riferisce effettivamente all'oggetto originale. – sellibitze

+0

@sellibitze: 12.2/5. Un'idea chiave è che le espressioni intermedie sono xvalue, non riferimenti, quindi il riferimento "riferimento è limitato" si riferisce esclusivamente agli oggetti nominati. (E il valore restituito di una funzione dichiarata con tipo di riferimento, che è un caso escluso.) – Potatoswatter

0

Il problema della sicurezza xvalue può essere risolto in qualche modo fornendo un'alternativa per l'uso all'interno di espressioni. Le questioni sono completamente diversi, ora noi facciamo non vuole un risultato xValue e possibile utilizzare una funzione:

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)); 

Ora il tipo deve essere default-costruibile, e il manichino è sempre costruito. La copia viene valutata condizionatamente. Non penso che vorrei davvero affrontare il codice che ha fatto troppo di questo.

Boost Optional può aiutare un po '; Si richiede solo 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; 
} 

opzionale è utile, ma ha una certa sovrapposizione con i sindacati C++ 0x. Non è troppo difficile da reimplementare.

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; 
} 

La classe optional_union è sufficiente solo per questa applicazione ... ovviamente potrebbe essere ampliato un sacco.

+1

Dato che hai già messo "il lavoro" in una funzione separata per il tuo primo esempio, potresti anche scrivere 'if (modify) {work (myA); } else {A copy = myA; lavorare (copia); } ';-) – sellibitze

+0

@sellibitze: l'oggetto è quello di evitare la duplicazione del codice. – Potatoswatter