I riferimenti rvalue a const hanno qualche utilità?


Risposte:


78

Occasionalmente sono utili. La bozza C ++ 0x stessa li utilizza in alcuni punti, ad esempio:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

I due sovraccarichi precedenti assicurano che l'altra ref(T&)e le cref(const T&)funzioni non si leghino a rvalues ​​(cosa altrimenti possibile).

Aggiornare

Ho appena controllato lo standard ufficiale N3290 , che purtroppo non è disponibile pubblicamente, e contiene in 20.8 Oggetti funzione [function.objects] / p2:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Quindi ho controllato la bozza post-C ++ 11 più recente, che è disponibile pubblicamente, N3485 , e in 20.8 Oggetti funzione [function.objects] / p2 dice ancora:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Guardando cppreference sembra che non sia più così. Qualche idea sul perché? Vengono utilizzati altri posti const T&&?
Pubby

60
Perché hai incluso lo stesso codice nella tua risposta tre volte? Ho provato a trovare una differenza per troppo tempo.
typ1232

9
@ typ1232: Sembra che ho aggiornato la risposta quasi 2 anni dopo aver risposto, a causa delle preoccupazioni nei commenti che le funzioni a cui si fa riferimento non apparissero più. Ho fatto un copia / incolla da N3290 e dall'ultima bozza N3485 per mostrare che le funzioni apparivano ancora. Usare copia / incolla, nella mia mente in quel momento, era il modo migliore per assicurarmi che più occhi dei miei potessero confermare che non stavo trascurando qualche piccolo cambiamento in queste firme.
Howard Hinnant

1
@kevinarpe: Gli "altri overload" (non mostrati qui, ma nello standard) accettano riferimenti a lvalue e non vengono eliminati. Gli overload mostrati qui sono una corrispondenza migliore per rvalues ​​rispetto agli overload che accettano riferimenti a lvalue. Quindi gli argomenti rvalue si legano agli overload mostrati qui e quindi causano un errore in fase di compilazione perché questi overload vengono eliminati.
Howard Hinnant

1
L'uso di const T&&impedisce a qualcuno di usare stupidamente gli argomenti del modello esplicito del modulo ref<const A&>(...). Non è un argomento molto forte, ma il costo di const T&&over T&&è piuttosto minimo.
Howard Hinnant

6

La semantica per ottenere un riferimento const rvalue (e non per =delete) è per dire:

  • non supportiamo l'operazione per lvalues!
  • anche se, continuiamo a copiare , perché non possiamo spostare la risorsa passata, o perché non c'è alcun significato reale per "spostarla".

Il seguente caso d'uso avrebbe potuto essere IMHO un buon caso d'uso per il riferimento rvalue a const , sebbene il linguaggio abbia deciso di non adottare questo approccio (vedere il post SO originale ).


Il caso: costruttore di puntatori intelligenti da puntatore grezzo

Di solito sarebbe consigliabile usare make_uniquee make_shared, ma entrambi unique_ptre shared_ptrpossono essere costruiti da un puntatore grezzo. Entrambi i costruttori ottengono il puntatore per valore e lo copiano. Entrambi consentono (cioè nel senso di: non impedire ) un uso continuativo del puntatore originale passato loro nel costruttore.

Il codice seguente viene compilato e risulta con double free :

int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;

Entrambi unique_ptre shared_ptrpotrebbero impedire quanto sopra se i rispettivi costruttori si aspettassero di ottenere il puntatore non elaborato come valore const , ad esempio per unique_ptr:

unique_ptr(T* const&& p) : ptr{p} {}

In tal caso il doppio codice libero sopra non verrebbe compilato, ma quanto segue:

std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) };     // ok, rvalue

Nota che ptrpotrebbe ancora essere utilizzato dopo che è stato spostato, quindi il potenziale bug non è completamente scomparso. Ma se all'utente viene richiesto di chiamare std::moveun bug del genere, rientrerebbe nella regola comune di: non utilizzare una risorsa che è stata spostata.


Ci si può chiedere: OK, ma perché T* const&& p ?

Il motivo è semplice, per consentire la creazione di unique_ptr da const pointer . Ricorda che il riferimento const rvalue è più generico del semplice riferimento rvalue poiché accetta sia conste non-const. Quindi possiamo consentire quanto segue:

int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };

questo non andrebbe se ci aspettassimo solo un riferimento rvalue (errore di compilazione: impossibile associare const rvalue a rvalue ).


Comunque, è troppo tardi per proporre una cosa del genere. Ma questa idea presenta un uso ragionevole di un riferimento rvalue a const .


Ero tra persone con idee simili, ma non avevo il supporto per andare avanti.
Red.Wave

"auto p = std :: unique_ptr {std :: move (ptr)};" non si compila con l'errore "deduzione dell'argomento del modello di classe non riuscita". Penso che dovrebbe essere "unique_ptr <int>".
Zehui Lin

4

Sono consentite e anche le funzioni classificate in base a const, ma poiché non è possibile spostarsi dall'oggetto const a cui fa riferimento const Foo&&, non sono utili.


Che cosa intendi esattamente con il commento "classificato"? Qualcosa a che fare con la risoluzione del sovraccarico, immagino?
fredoverflow

Perché non puoi muoverti da un const rvalue-ref, se il tipo specificato ha un ctor di movimento che accetta un const rvalue-ref?
Fred Nurk

6
@FredOverflow, la classifica del sovraccarico è questa:const T&, T&, const T&&, T&&
Gene Bushuyev

2
@Fred: come ti muovi senza modificare la fonte?
fredoverflow

3
@Fred: i membri di dati mutevoli, o forse lo spostamento per questo tipo ipotetico non richiede la modifica dei membri di dati.
Fred Nurk

2

Oltre a std :: ref , la libreria standard utilizza anche il riferimento const rvalue in std :: as_const per lo stesso scopo.

template <class T>
void as_const(const T&&) = delete;

Viene anche usato come valore di ritorno in std :: optional quando si ottiene il valore avvolto:

constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;

Così come in std :: get :

template <class T, class... Types>
constexpr const T&& get(const std::variant<Types...>&& v);
template< class T, class... Types >
constexpr const T&& get(const tuple<Types...>&& t) noexcept;

Questo è presumibilmente per mantenere la categoria del valore e la costanza del wrapper quando si accede al valore avvolto.

Ciò fa la differenza se le funzioni qualificate ref rvalue possono essere chiamate sull'oggetto avvolto. Detto questo, non conosco alcun utilizzo per le funzioni qualificate ref rvalue const.


1

Non riesco a pensare a una situazione in cui ciò sarebbe utile direttamente, ma potrebbe essere utilizzato indirettamente:

template<class T>
void f(T const &x) {
  cout << "lvalue";
}
template<class T>
void f(T &&x) {
  cout << "rvalue";
}

template<class T>
void g(T &x) {
  f(T());
}

template<class T>
void h(T const &x) {
  g(x);
}

La T in g è T const, quindi x di f è una T const &&.

È probabile che questo si traduca in un errore di comile in f (quando tenta di spostare o utilizzare l'oggetto), ma f potrebbe prendere un rvalue-ref in modo che non possa essere chiamato su lvalues, senza modificare il rvalue (come nel troppo semplice esempio sopra).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.