Per dirla in maniera rozza, sono i valori che in un assegnamento (L=R) possono stare rispettivamente a sinistra (lvalue - left-value) e destra (rvalue - right-value) del segno di uguale.
(naturalmente un lvalue può anche stare a destra, ma non viceversa)
I literal (=i valori inseriti direttamente nel codice) sono ovviamente degli rvalue, sia per ragioni di senso (che senso avrebbe scrivere 5=a?) che per ragioni di codice generato (in genere i literal sono inseriti direttamente nell'assembly generato come valori immediate); idem i valori restituiti dalle funzioni per valore (non ha senso ad esempio scrivere
codice:
int funzione()
{
return 10;
}
// ...
funzione()=5;
)
I reference invece sono degli lvalue, perché "nascondono" dietro un indirizzo di memoria in cui vanno a memorizzare quello che ci viene assegnato:
codice:
int a;
int & funzione()
{
return a;
}
// ...
funzione() = 10; // 10 va a finire in a, dato che funzione() ha restituito un reference ad a
motivo per cui degli lvalue si può ottenere l'indirizzo con l'operatore &, mentre degli rvalue no.
Dato che i reference sono degli lvalue per definizione, li si può inizializzare solo con degli rvalue (ricorda che un reference è un alias per ciò a cui viene inizializzato), altrimenti la semantica del linguaggio andrebbe all'aria.
codice:
int & funzione()
{
return 5; // <-- qui ottieni un errore di compilazione, stai inizializzando il reference restituito con un lvalue
}
funzione()=10; // che cavolo mi vorrebbe significare? Dove verrebbe copiato questo 10?
Eccezione: i const reference; dato che un const reference (ad esempio const int & ) non può essere destinatario di un assegnamento, il problema di cui sopra non si pone, per cui un const & può essere inizializzato con un lvalue (e quindi con un literal o con un temporary):
codice:
int funzione()
{
return 5;
}
void funzione2(const int & a)
{
// a è const, per cui non ci posso assegnare nulla, dunque può riferirsi "nascostamente" ad un lvalue
}
funzione2(funzione()); // no problem, il valore restituito è un lvalue ma funzione2 accetta un const int &
funzione2(5); // idem
In questo esempio il compilatore probabilmente farà un push sullo stack del valore restituito da funzione o del literal e ne passerà l'indirizzo "dentro il reference" a funzione2.
Nota che un const reference in realtà è un oggetto strano, perché non può stare a sinistra di un assegnamento, ma essendo un reference di fatto avrà un indirizzo nascosto dietro. Per questo motivo la terminologia lvalue/rvalue è incompleta, e si parla talvolta di modifiable/nonmodifiable lvalue per distinguere gli lvalue "normali" dai const reference.