Qual è un caso d’uso per sovraccaricare le funzioni membro sui qualificatori di riferimento?

C ++ 11 rende ansible il sovraccarico delle funzioni membro in base ai qualificatori di riferimento:

class Foo { public: void f() &; // for when *this is an lvalue void f() &&; // for when *this is an rvalue }; Foo obj; obj.f(); // calls lvalue overload std::move(obj).f(); // calls rvalue overload 

Capisco come funziona, ma a che serve?

Vedo che N2819 ha proposto di limitare la maggior parte degli operatori di assegnazione nella libreria standard agli obiettivi lvalue (cioè, aggiungendo ” & ” qualificatori di riferimento agli operatori di assegnazione), ma questo è stato respinto . Quindi questo è stato un potenziale caso d’uso in cui la commissione ha deciso di non seguirlo. Quindi, ancora, qual è un caso d’uso ragionevole?

In una class che fornisce getter di riferimento, il sovraccarico del qualificatore ref può triggersre la semantica del movimento quando si estrae da un valore rvalue. Per esempio:

 class some_class { huge_heavy_class hhc; public: huge_heavy_class& get() & { return hhc; } huge_heavy_class const& get() const& { return hhc; } huge_heavy_class&& get() && { return std::move(hhc); } }; some_class factory(); auto hhc = factory().get(); 

Questo sembra un grande sforzo da investire solo per avere la syntax più breve

 auto hhc = factory().get(); 

avere lo stesso effetto di

 auto hhc = std::move(factory().get()); 

EDIT: ho trovato il documento originale della proposta , fornisce tre esempi motivanti:

  1. operator = vincolante operator = a lvalue (risposta di TemplateRex)
  2. Abilitazione della mossa per i membri (fondamentalmente questa risposta)
  3. operator & vincolante operator & ai lvalue. Suppongo che sia sensato assicurare che il “punta” sia più probabile che sia vivo quando il “puntatore” viene infine dereferenziato:
 struct S { T operator &() &; }; int main() { S foo; auto p1 = &foo; // Ok auto p2 = &S(); // Error } 

Non posso dire di aver mai usato personalmente un operator& sovraccarico.

Un caso d’uso è vietare l’assegnazione ai provvisori

  // can only be used with lvalues T& operator*=(T const& other) & { /* ... */ return *this; } // not possible to do (a * b) = c; T operator*(T const& lhs, T const& rhs) { return lhs *= rhs; } 

mentre non usare la qualifica di riferimento ti lascerebbe la scelta tra due cattivi

  T operator*(T const& lhs, T const& rhs); // can be used on rvalues const T operator*(T const& lhs, T const& rhs); // inhibits move semantics 

La prima opzione consente di spostare la semantica, ma agisce in modo diverso sui tipi definiti dall’utente che sui builtin (non fa come fanno gli inte). La seconda scelta fermerebbe l’asserzione ma eliminerebbe la semantica del movimento (ansible colpo di prestazioni per esempio la moltiplicazione della matrice).

I collegamenti di @dyp nei commenti forniscono anche una discussione estesa sull’uso dell’altro ( && ) overload, che può essere utile se si desidera assegnare a riferimenti (lvalue o rvalue).

Se f () ha bisogno di un Foo temp che è una copia di questo e modificato, puoi modificare il temp this mentre invece non puoi altrimenti

Da un lato puoi usarli per impedire funzioni semanticamente prive di senso di chiamare i provvisori per essere chiamati, come operator= o funzioni che mutano lo stato interno e restituiscono void , aggiungendo & come qualificatore di riferimento.

D’altra parte è ansible utilizzarlo per ottimizzazioni come lo spostamento di un membro fuori dall’object come valore di ritorno quando si ha un riferimento di rvalue, ad esempio una funzione getName potrebbe restituire uno std::string const& o std::string&& sul qualificatore di riferimento.

Un altro caso d’uso potrebbe essere operatori e funzioni che restituiscono un riferimento all’object originale come Foo& operator+=(Foo&) che potrebbero essere specializzati per restituire invece un riferimento rvalue, rendendo il risultato mobile, che sarebbe di nuovo un’ottimizzazione.

TL; DR: Usalo per prevenire l’uso scorretto di una funzione o per l’ottimizzazione.