In che modo l’auto && estenderà la durata dell’object temporaneo?

Il codice seguente illustra la mia preoccupazione:

#include  struct O { ~O() { std::cout << "~O()\n"; } }; struct wrapper { O const& val; ~wrapper() { std::cout << "~wrapper()\n"; } }; struct wrapperEx // with explicit ctor { O const& val; explicit wrapperEx(O const& val) : val(val) {} ~wrapperEx() { std::cout << "~wrapperEx()\n"; } }; template T&& f(T&& t) { return std::forward(t); } int main() { std::cout << "case 1-----------\n"; { auto&& a = wrapper{O()}; std::cout << "end-scope\n"; } std::cout << "case 2-----------\n"; { auto a = wrapper{O()}; std::cout << "end-scope\n"; } std::cout << "case 3-----------\n"; { auto&& a = wrapper{f(O())}; std::cout << "end-scope\n"; } std::cout << "case Ex-----------\n"; { auto&& a = wrapperEx{O()}; std::cout << "end-scope\n"; } return 0; } 

Guardalo dal vivo qui .

Si dice che l’ auto&& estenderà la durata dell’object temporaneo, ma non riesco a trovare le parole standard su questa regola, almeno non in N3690.

La più rilevante potrebbe essere la sezione 12.2.5 sull’object temporaneo, ma non esattamente quello che sto cercando.

Quindi, la regola di estensione auto && life-time si applica a tutti gli oggetti temporanei coinvolti nell’espressione o solo al risultato finale?

Più specifico, a.val è garantito che sia valido (non penzolante) prima di raggiungere la fine dello scope nel caso 1?

Modifica: ho aggiornato l’esempio per mostrare più casi (3 ed ex).

Vedrai che solo nel caso 1 la durata di O è estesa.

Allo stesso modo che fa riferimento a const :

 const auto& a = wrapper{O()}; 

o

 const wrapper& a = wrapper{O()}; 

o anche

 wrapper&& a = wrapper{O()}; 

Più specifico, a.val è garantito che sia valido (non penzolante) prima di raggiungere la fine dello scope nel caso 1?

Sì.

Non c’è (quasi) nulla di particolarmente importante riguardo l’ auto qui. È solo un segnaposto per il tipo corretto ( wrapper ) che viene dedotto dal compilatore. Il punto principale è il fatto che il temporaneo è legato a un riferimento.

Per maggiori dettagli vedi A Candidato Per il “Most important const” che cito:

Normalmente, un object temporaneo dura solo fino alla fine dell’espressione completa in cui appare. Tuttavia, C ++ specifica deliberatamente che bind un object temporaneo a un riferimento a const sullo stack allunga la vita del temporaneo alla durata del riferimento stesso

L’articolo riguarda C ++ 03 ma l’argomento è ancora valido: un temporaneo può essere associato a un riferimento a const (ma non a un riferimento a non- const ). In C ++ 11, un temporaneo può anche essere associato a un riferimento di rvalue. In entrambi i casi, la durata del temporaneo viene estesa alla durata del riferimento.

Le parti rilevanti dello standard C ++ 11 sono esattamente quelle indicate nell’OP, ovvero 12.2 p4 e p5:

4 – Ci sono due contesti in cui i provvisori vengono distrutti in un punto diverso rispetto alla fine dell’espressione completa. Il primo contesto è […]

5 – Il secondo contesto è quando un riferimento è legato a un temporaneo. […]

(Ci sono alcune eccezioni nei punti elenco seguendo queste linee).

Aggiornamento : (seguendo il commento di texasbruce).

Il motivo per cui l’ O nel caso 2 ha una vita breve è che abbiamo auto a = wrapper{O()}; (vedi, non c’è & qui) e quindi il temporaneo non è legato a un riferimento. Il temporaneo è, in realtà, copiato in a usando il costruttore di copia generato dal compilatore. Pertanto, il temporaneo non ha la sua durata espansa e muore alla fine dell’espressione completa in cui appare.

C’è un pericolo in questo particolare esempio perché wrapper::val è un riferimento. Il compilatore-costruttore di wrapper generato dal compilatore associa a.val allo stesso object a cui è associato il membro val del temporaneo. Questo object è anche temporaneo ma di tipo O Quindi, quando quest’ultimo muore temporaneamente, vediamo ~O() sullo schermo e a.val dondola!

Caso contrastante 2 con questo:

 std::cout << "case 3-----------\n"; { O o; auto a = wrapper{o}; std::cout << "end-scope\n"; } 

L'output è (quando compilato con gcc usando l'opzione -fno-elide-constructors )

 case 3----------- ~wrapper() end-scope ~wrapper() ~O() 

Ora il wrapper temporaneo ha il suo membro val associato a o . Si noti che o non è temporaneo. Come ho detto, a è una copia del wrapper temporaneo e a.val si lega anche a o . Prima che lo scope finisca, il wrapper temporaneo muore e vediamo il primo ~wrapper() sullo schermo.

Quindi l'ambito termina e otteniamo l' end-scope . Ora, a e o devono essere distrutti nell'ordine inverso di costruzione, quindi vediamo ~wrapper() quando muore e infine ~O() quando è ora di o . Questo dimostra che a.val non a.val .

( -fno-elide-constructors finale: ho usato -fno-elide-constructors per impedire un'ottimizzazione relativa alla costruzione di copie che complicherebbe la discussione qui, ma questa è un'altra storia .)