Immagino di no, ma vorrei confermare. C'è qualche utilità per const Foo&&
, dov'è Foo
un tipo di classe?
Immagino di no, ma vorrei confermare. C'è qualche utilità per const Foo&&
, dov'è Foo
un tipo di classe?
Risposte:
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;
const T&&
?
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.
La semantica per ottenere un riferimento const rvalue (e non per =delete
) è per dire:
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 ).
Di solito sarebbe consigliabile usare make_unique
e make_shared
, ma entrambi unique_ptr
e shared_ptr
possono 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_ptr
e shared_ptr
potrebbero 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 ptr
potrebbe ancora essere utilizzato dopo che è stato spostato, quindi il potenziale bug non è completamente scomparso. Ma se all'utente viene richiesto di chiamare std::move
un 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 const
e 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 .
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.
const T&, T&, const T&&, T&&
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.
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).
const&&
sono molto importanti, anche se non dice perché: youtube.com/watch?v=JhgWFYfdIho#t=54m20s