In primo luogo, "qualificazioni di ref per * this" è solo una "dichiarazione di marketing". Il tipo di *this
non cambia mai, vedi il fondo di questo post. È molto più facile capirlo con questa formulazione però.
Successivamente, il codice seguente sceglie la funzione da chiamare in base al qualificatore di riferimento del "parametro oggetto implicito" della funzione † :
// t.cpp
#include <iostream>
struct test{
void f() &{ std::cout << "lvalue object\n"; }
void f() &&{ std::cout << "rvalue object\n"; }
};
int main(){
test t;
t.f(); // lvalue
test().f(); // rvalue
}
Produzione:
$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object
Il tutto viene fatto per permetterti di sfruttare il fatto quando l'oggetto su cui viene chiamata la funzione è un valore (ad esempio temporaneo senza nome). Prendi il seguente codice come ulteriore esempio:
struct test2{
std::unique_ptr<int[]> heavy_resource;
test2()
: heavy_resource(new int[500]) {}
operator std::unique_ptr<int[]>() const&{
// lvalue object, deep copy
std::unique_ptr<int[]> p(new int[500]);
for(int i=0; i < 500; ++i)
p[i] = heavy_resource[i];
return p;
}
operator std::unique_ptr<int[]>() &&{
// rvalue object
// we are garbage anyways, just move resource
return std::move(heavy_resource);
}
};
Questo potrebbe essere un po 'inventato, ma dovresti avere l'idea.
Si noti che è possibile combinare i qualificatori cv ( const
e volatile
) e i qualificatori ref ( &
e &&
).
Nota: molte citazioni standard e spiegazioni sulla risoluzione del sovraccarico dopo qui!
† Per capire come funziona e perché la risposta di @Nicol Bolas è almeno in parte sbagliata, dobbiamo scavare un po 'nello standard C ++ (la parte che spiega perché la risposta di @Nicol è sbagliata è in fondo, se sei interessato solo a quello).
La funzione che verrà chiamata è determinata da un processo chiamato risoluzione di sovraccarico . Questo processo è abbastanza complicato, quindi toccheremo solo la parte che è importante per noi.
Innanzitutto, è importante vedere come funziona la risoluzione di sovraccarico per le funzioni membro:
§13.3.1 [over.match.funcs]
p2 L'insieme di funzioni candidate può contenere sia funzioni membro che non membro da risolvere sullo stesso elenco di argomenti. In modo che gli elenchi di argomenti e parametri siano comparabili all'interno di questo insieme eterogeneo, si ritiene che una funzione membro abbia un parametro aggiuntivo, chiamato parametro oggetto implicito, che rappresenta l'oggetto per il quale è stata chiamata la funzione membro . [...]
p3 Allo stesso modo, quando appropriato, il contesto può costruire un elenco di argomenti che contiene un argomento oggetto implicito per indicare l'oggetto su cui operare.
Perché dobbiamo anche confrontare le funzioni membro e non membro? Operatore sovraccarico, ecco perché. Considera questo:
struct foo{
foo& operator<<(void*); // implementation unimportant
};
foo& operator<<(foo&, char const*); // implementation unimportant
Vorresti sicuramente quanto segue per chiamare la funzione gratuita, vero?
char const* s = "free foo!\n";
foo f;
f << s;
Ecco perché le funzioni membro e non membro sono incluse nel cosiddetto sovraccarico. Per rendere la risoluzione meno complicata, esiste la parte in grassetto della citazione standard. Inoltre, questo è il bit importante per noi (stessa clausola):
p4 Per le funzioni membro non statiche, il tipo del parametro oggetto implicito è
"Lvalue reference to cv X
" per le funzioni dichiarate senza un qualificatore di riferimento o con il &
qualificatore di riferimento
“Rvalue reference to cv X
” per le funzioni dichiarate con il &&
qualificatore di riferimento
dove X
è la classe di cui la funzione è membro e cv è la qualifica cv sulla dichiarazione della funzione membro. [...]
p5 Durante la risoluzione di sovraccarico [...] il parametro oggetto implicito [...] mantiene la sua identità poiché le conversioni sull'argomento corrispondente devono rispettare queste regole aggiuntive:
nessun oggetto temporaneo può essere introdotto per contenere l'argomento per il parametro oggetto implicito; e
nessuna conversione definita dall'utente può essere applicata per ottenere una corrispondenza di tipo con essa
[...]
(L'ultimo bit significa solo che non è possibile imbrogliare la risoluzione di sovraccarico in base alle conversioni implicite dell'oggetto su cui viene chiamata una funzione membro (o operatore).)
Facciamo il primo esempio all'inizio di questo post. Dopo la trasformazione di cui sopra, il sovraccarico appare in questo modo:
void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'
Quindi l'elenco degli argomenti, contenente un argomento oggetto implicito , viene confrontato con l'elenco dei parametri di ogni funzione contenuta nel set di overload. Nel nostro caso, l'elenco degli argomenti conterrà solo tale argomento. Vediamo come appare:
// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
// kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
// taken out of overload-set
Se, dopo aver verificato tutti i sovraccarichi nel set, ne rimane solo uno, la risoluzione del sovraccarico ha esito positivo e viene chiamata la funzione collegata a quel sovraccarico trasformato. Lo stesso vale per la seconda chiamata a 'f':
// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
// taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
// kept in overload-set
Si noti comunque che, se non avessimo fornito alcuna ref-qualificazione (e come tale non sovraccaricato la funzione), che f1
sarebbe corrispondere a un rvalue (ancora §13.3.1
):
p5 [...] Per le funzioni membro non statiche dichiarate senza qualificatore di riferimento , si applica una regola aggiuntiva:
- anche se il parametro oggetto implicito non è
const
-qualificato, un valore può essere associato al parametro fintanto che sotto tutti gli altri aspetti l'argomento può essere convertito nel tipo di parametro oggetto implicito.
struct test{
void f() { std::cout << "lvalue or rvalue object\n"; }
};
int main(){
test t;
t.f(); // OK
test().f(); // OK too
}
Ora, sul perché la risposta di @Nicol è almeno in parte sbagliata. Lui dice:
Si noti che questa dichiarazione cambia il tipo di *this
.
Questo è sbagliato, *this
è sempre un valore:
§5.3.1 [expr.unary.op] p1
L' *
operatore unario esegue il riferimento indiretto : l'espressione a cui viene applicata deve essere un puntatore a un tipo di oggetto o un puntatore a un tipo di funzione e il risultato è un valore che si riferisce all'oggetto o alla funzione a cui punta l'espressione.
§9.3.2 [class.this] p1
Nel corpo di una funzione membro non statica (9.3), la parola chiave this
è un'espressione di valore il cui valore è l'indirizzo dell'oggetto per cui viene chiamata la funzione. Il tipo di this
in una funzione membro di una classe X
è X*
. [...]