Che cosa sono valori, valori, valori x, valori e valori?


1356

In C ++ 03, un'espressione è o un valore o un valore .

In C ++ 11, un'espressione può essere un:

  1. rvalue
  2. lvalue
  3. xValue
  4. glvalue
  5. prvalue

Due categorie sono diventate cinque categorie.

  • Quali sono queste nuove categorie di espressioni?
  • In che modo queste nuove categorie si relazionano con le categorie rvalue e lvalue esistenti?
  • Le categorie rvalue e lvalue in C ++ 0x sono le stesse di C ++ 03?
  • Perché sono necessarie queste nuove categorie? Gli dei del WG21 stanno solo cercando di confonderci semplici mortali?

9
@Philip Potter: in C ++ 03? Sì. Un lvalue può essere usato come rvalue perché esiste una conversione da lvalue a rvalue standard.
James McNellis,

14
@Tyler: "Se puoi assegnargli, è un valore, altrimenti è un valore." -> Sbagliato, è possibile assegnare a rvalues classe: string("hello") = string("world").
Fredoverflow,

4
Si noti che questa è la categoria di valore. Esistono più proprietà che le espressioni possono avere. Questi includono bit-field (true / false), temporanei (true / false) e type (il tipo di esso).
Johannes Schaub - lett.

30
Penso che il link di Fred sopra sia migliore di una qualsiasi delle risposte qui. Il collegamento è morto, però. È stato spostato in: stroustrup.com/terminology.pdf
R. Martinho Fernandes,

74
in C ++ anche i tuoi tipi hanno tipi
nielsbot,

Risposte:


634

Immagino che questo documento possa servire come una breve introduzione: n3055

L'intero massacro è iniziato con la semantica della mossa. Una volta che abbiamo espressioni che possono essere spostate e non copiate, le regole improvvisamente facili da comprendere richiedono una distinzione tra espressioni che possono essere spostate e in quale direzione.

Da quello che immagino in base alla bozza, la distinzione del valore r / l rimane invariata, solo nel contesto dello spostamento delle cose diventa confusa.

Sono necessari? Probabilmente no se desideriamo rinunciare alle nuove funzionalità. Ma per consentire una migliore ottimizzazione dovremmo probabilmente abbracciarli.

Citando n3055 :

  • Un valore (cosiddetto, storicamente, perché i valori potrebbero apparire sul lato sinistro di un'espressione di assegnazione) designa una funzione o un oggetto. [Esempio: Se Eè un'espressione di tipo puntatore, allora *E è un'espressione lvalue che si riferisce all'oggetto o alla funzione a cui E punta. Come altro esempio, il risultato della chiamata di una funzione il cui tipo restituito è un riferimento lvalue è un lvalue.]
  • Un valore x (un valore "eXpiring") si riferisce anche a un oggetto, di solito vicino alla fine della sua vita (in modo che le sue risorse possano essere spostate, ad esempio). Un xvalue è il risultato di alcuni tipi di espressioni che coinvolgono riferimenti a rvalue. [Esempio: il risultato della chiamata di una funzione il cui tipo restituito è un riferimento di valore è un valore x.]
  • Un valore (valore “generalizzato”) è un valore o un valore x .
  • Un valore (cosiddetto, storicamente, perché i valori potrebbero apparire sul lato destro di un'espressione di assegnazione) è un valore x, un oggetto temporaneo o un oggetto secondario o un valore che non è associato a un oggetto.
  • Un valore (valore "puro") è un valore che non è un valore x. [Esempio: il risultato della chiamata di una funzione il cui tipo restituito non è un riferimento è un valore]

Il documento in questione è un ottimo riferimento per questa domanda, perché mostra i cambiamenti esatti nello standard che sono avvenuti a seguito dell'introduzione della nuova nomenclatura.


Grazie, questa risposta è davvero utile! Ma il mio compilatore non è d'accordo con i tuoi esempi di valori x e valori; sono l'esatto contrario. Il ritorno per riferimento al valore mi dà un valore e il ritorno per valore mi dà un valore x. Li hai fatti confondere o il mio banco di prova è rotto? Ho provato questo con GCC 4.6.1, clang (da svn) e MSVC, e mostrano tutti lo stesso comportamento.
Kim Gräsman,

Oops, ho appena seguito il link e ho notato che gli esempi sono nella fonte. Vado a cercare la mia copia dello standard e controllerò cosa dice ...
Kim Gräsman

4
Uso le macro da qui per testare varie espressioni: stackoverflow.com/a/6114546/96963 Potrebbe essere che diagnostichino erroneamente le cose.
Kim Gräsman,

1
L'aggiunta del valore x non è per la semantica di spostamento. Solo con entrambi i valori lvalue e rvalue, la semantica di movimento, il riferimento perfetto in avanti e il valore rvalue funzionano ancora bene. Penso che xvalue sia solo per l'operatore decltype: se l'espressione dell'operando è xvalue, il decltype fornisce il tipo di riferimento rvalue.
ligando

1
@MuhamedCicak "Ogni espressione è o un valore o un valore": è vero; e lo standard (o il documento n3055) non dice che è falso. Il motivo per cui questa frase è stata cancellata è che stavi esaminando le modifiche tra due versioni del documento. La frase fu rimossa perché divenne superflua dopo l'aggiunta di una spiegazione più precisa.
massimo

337

Quali sono queste nuove categorie di espressioni?

L' FCD (n3092) ha una descrizione eccellente:

- Un lvalue (così chiamato, storicamente, perché i lvalues ​​potrebbero apparire sul lato sinistro di un'espressione di assegnazione) designa una funzione o un oggetto. [Esempio: se E è un'espressione di tipo puntatore, allora * E è un'espressione lvalore che si riferisce all'oggetto o alla funzione a cui E punta. Come altro esempio, il risultato della chiamata di una funzione il cui tipo restituito è un riferimento lvalue è un lvalue. —Esempio]

- Un valore x (un valore "eXpiring") si riferisce anche a un oggetto, di solito vicino alla fine della sua vita (in modo che le sue risorse possano essere spostate, ad esempio). Un xvalue è il risultato di alcuni tipi di espressioni che coinvolgono riferimenti rvalue (8.3.2). [Esempio: il risultato della chiamata di una funzione il cui tipo restituito è un riferimento di valore è un valore x. —Esempio]

- Un valore (valore “generalizzato”) è un valore o un valore x.

- Un valore (così chiamato, storicamente, perché i valori possono apparire sul lato destro delle espressioni di un compito) è un valore x, un oggetto temporaneo (12.2) o un oggetto secondario o un valore che non è associato a un oggetto.

- Un valore (valore "puro") è un valore che non è un valore x. [Esempio: il risultato della chiamata di una funzione il cui tipo restituito non è un riferimento è un valore. Anche il valore di un valore letterale come 12, 7.3e5 o true è un valore. —End esempio]

Ogni espressione appartiene esattamente a una delle classificazioni fondamentali in questa tassonomia: lvalue, xvalue o prvalue. Questa proprietà di un'espressione è chiamata la sua categoria di valore. [Nota: la discussione di ciascun operatore integrato nella clausola 5 indica la categoria del valore che produce e le categorie di valore degli operandi che si aspetta. Ad esempio, gli operatori di assegnazione incorporati si aspettano che l'operando di sinistra sia un valore e che l'operando di destra sia un valore e di conseguenza produca un valore. Gli operatori definiti dall'utente sono funzioni e le categorie di valori che si aspettano e producono sono determinate dai loro parametri e tipi di ritorno. - nota

Ti suggerisco di leggere l'intera sezione 3.10 Valori e valori però.

In che modo queste nuove categorie si relazionano con le categorie rvalue e lvalue esistenti?

Ancora:

Tassonomia

Le categorie rvalue e lvalue in C ++ 0x sono le stesse di C ++ 03?

La semantica dei valori si è evoluta in particolare con l'introduzione della semantica di movimento.

Perché sono necessarie queste nuove categorie?

In modo che la costruzione / assegnazione del movimento possa essere definita e supportata.


54
Mi piace il diagramma qui. Penso che potrebbe essere utile iniziare la risposta con "Ogni espressione appartiene esattamente a una delle classificazioni fondamentali in questa tassonomia: lvalue, xvalue o prvalue". Quindi è facile usare il diagramma per mostrare che queste tre classi fondamentali sono combinate per creare glvalue e rvalue.
Aaron McDaid,

2
"is glvalue" equivale a "is prvalue" e "is rvalue" equivale a "is lvalue".
Vladimir Reshetnikov,

2
Questo mi ha aiutato di più: bajamircea.github.io/assets/2016-04-07-move-forward/… (diagramma di Venn delle categorie di valore)
John P

1
@AaronMcDaid Ciao, domanda veloce se tu / qualcuno può rispondere ... Perché non nominare glvaluecome lvaluee lvaluecome plvalue, per essere coerenti?
Vijay Chavda,

184

Inizierò con la tua ultima domanda:

Perché sono necessarie queste nuove categorie?

Lo standard C ++ contiene molte regole che riguardano la categoria di valori di un'espressione. Alcune regole fanno una distinzione tra lvalue e rvalue. Ad esempio, quando si tratta di risoluzione di sovraccarico. Altre regole fanno una distinzione tra glvalue e prvalue. Ad esempio, è possibile avere un valore di soglia con un tipo incompleto o astratto ma non esiste un valore con un tipo incompleto o astratto. Prima di avere questa terminologia, le regole che in realtà hanno bisogno di distinguere tra glvalue / prvalue si riferivano a lvalue / rvalue e erano o involontariamente sbagliate o contenevano molte spiegazioni ed eccezioni alla regola alla "... a meno che il valore non sia dovuto a senza nome riferimento valore ... ". Quindi, sembra una buona idea dare il proprio nome ai concetti di valori e valori.

Quali sono queste nuove categorie di espressioni? In che modo queste nuove categorie si relazionano con le categorie rvalue e lvalue esistenti?

Abbiamo ancora i termini lvalue e rvalue compatibili con C ++ 98. Abbiamo appena diviso i valori in due sottogruppi, valori x e valori, e ci riferiamo a valori e valori x come valori. Gli Xvalues ​​sono un nuovo tipo di categoria di valori per riferimenti di valori senza nome. Ogni espressione è una di queste tre: lvalue, xvalue, prvalue. Un diagramma di Venn sarebbe simile a questo:

    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r

Esempi con funzioni:

int   prvalue();
int&  lvalue();
int&& xvalue();

Ma non dimenticare che i riferimenti ai valori nominali sono valori:

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}

165

Perché sono necessarie queste nuove categorie? Gli dei del WG21 stanno solo cercando di confonderci semplici mortali?

Non credo che le altre risposte (per quanto buone siano molte) catturino davvero la risposta a questa particolare domanda. Sì, queste categorie e simili esistono per consentire la semantica di spostamento, ma la complessità esiste per una ragione. Questa è l'unica regola inviolata per spostare le cose in C ++ 11:

Ti muoverai solo quando è indubbiamente sicuro farlo.

Ecco perché esistono queste categorie: essere in grado di parlare di valori dove è sicuro spostarsi da essi e di parlare di valori dove non lo è.

Nella prima versione dei riferimenti di valore r, il movimento avveniva facilmente. Troppo facilmente. Abbastanza facilmente che c'era un grande potenziale per spostare implicitamente cose quando l'utente non intendeva farlo.

Ecco le circostanze in cui è sicuro spostare qualcosa:

  1. Quando si tratta di un suo oggetto temporaneo o secondario. (Prvalue)
  2. Quando l'utente ha esplicitamente dichiarato di spostarlo .

Se lo fai:

SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};

Cosa fa questo? Nelle versioni precedenti delle specifiche, prima che arrivassero i 5 valori, ciò provocherebbe una mossa. Certo che lo fa. È stato passato un riferimento al valore al costruttore e quindi si lega al costruttore che accetta un riferimento al valore. Questo è ovvio.

C'è solo un problema con questo; non hai chiesto di spostarlo. Oh, potresti dire che &&avrebbe dovuto essere un indizio, ma ciò non cambia il fatto che abbia infranto la regola. valnon è temporaneo perché i provvisori non hanno nomi. Potresti aver prolungato la durata del temporaneo, ma ciò significa che non è temporaneo ; è proprio come qualsiasi altra variabile di stack.

Se non è temporaneo e non hai chiesto di spostarlo, lo spostamento è sbagliato.

La soluzione ovvia è fare valun valore. Ciò significa che non puoi muoverti da esso. Ok bene; è chiamato, quindi è un valore.

Una volta che lo fai, non puoi più dire che SomeType&&significa la stessa cosa ovunque. Ora hai fatto una distinzione tra riferimenti a valori nominali e riferimenti a valori nominali senza nome. Bene, i riferimenti ai valori nominali sono valori; quella era la nostra soluzione sopra. Quindi, cosa chiamiamo riferimenti a valori senza nome (il valore di ritorno Funcdall'alto)?

Non è un valore, perché non è possibile spostarsi da un valore. E abbiamo bisogno di essere in grado di muoversi restituendo una &&; in quale altro modo potresti dire esplicitamente di spostare qualcosa? Questo è ciò che std::moveritorna, dopo tutto. Non è un valore (vecchio stile), perché può trovarsi sul lato sinistro di un'equazione (le cose sono in realtà un po 'più complicate, vedi questa domanda e i commenti sotto). Non è né un valore né un valore; è un nuovo tipo di cose.

Ciò che abbiamo è un valore che puoi trattare come un valore, tranne per il fatto che è implicitamente mobile da. Lo chiamiamo un valore x.

Nota che gli xvalori sono ciò che ci fa guadagnare le altre due categorie di valori:

  • Un valore è in realtà solo il nuovo nome del precedente tipo di valore, ovvero sono i valori che non sono valori x.

  • I glvalues ​​sono l'unione di xvalues ​​e lvalues ​​in un gruppo, perché condividono molte proprietà in comune.

Quindi davvero, tutto si riduce a valori x e alla necessità di limitare il movimento esattamente e solo in determinati luoghi. Quei posti sono definiti dalla categoria rvalue; i valori sono le mosse implicite e gli xvalori sono le mosse esplicite ( std::moverestituisce un valore x).


11
@Thomas: è un esempio; non importa come crea il valore di ritorno. Ciò che conta è che ritorni a &&.
Nicol Bolas,

1
Nota: i valori possono trovarsi sul lato sinistro di un'equazione, anche - come in X foo(); foo() = X;... Per questo motivo fondamentale, non riesco proprio a seguire fino in fondo la risposta eccellente sopra, perché in realtà fai solo la distinzione tra il nuovo xvalue e il vecchio valore, basato sul fatto che può essere su lhs.
Dan Nissenbaum,

1
Xessere una classe; X foo();essendo una dichiarazione di funzione ed foo() = X();essendo una riga di codice. (Ho lasciato il secondo set di parentesi nella foo() = X();mia sopra commento.) Per una domanda che ho appena pubblicato con questo utilizzo evidenziato, vedo stackoverflow.com/questions/15482508/...
Dan Nissenbaum

1
@DanNissenbaum "xvalue non può trovarsi sul lato sinistro dell'espressione di assegnazione" - perché no? Vedi ideone.com/wyrxiT
Mikhail

1
Risposta illuminante. Questa è senza dubbio la migliore risposta qui. Mi ha dato la logica di introdurre le nuove categorie di valore e ciò che è accaduto prima.
Nikos,

136

IMHO, la migliore spiegazione del suo significato ci ha dato Stroustrup + prendere in considerazione esempi di Dániel Sándor e Mohan :

Stroustrup:

Ora ero seriamente preoccupato. Chiaramente eravamo diretti verso un vicolo cieco, un casino o entrambi. Ho trascorso l'ora di pranzo facendo un'analisi per vedere quali proprietà (dei valori) erano indipendenti. C'erano solo due proprietà indipendenti:

  • has identity - ie e indirizzo, un puntatore, l'utente può determinare se due copie sono identiche, ecc.
  • can be moved from - vale a dire che siamo autorizzati a partire da una "copia" in uno stato indeterminato, ma valido

Questo mi ha portato alla conclusione che ci sono esattamente tre tipi di valori (usando il trucco notazionale regex di usare una lettera maiuscola per indicare un negativo - ero di fretta):

  • iM: ha identità e non può essere spostato da
  • im: ha identità e può essere spostato da (ad es. il risultato del lancio di un valore in un riferimento valore)
  • Im: non ha identità e può essere spostato da.

    La quarta possibilità, IM(non ha identità e non può essere spostata) non è utile in C++(o, penso,) in qualsiasi altra lingua.

Oltre a queste tre classificazioni fondamentali dei valori, abbiamo due ovvie generalizzazioni che corrispondono alle due proprietà indipendenti:

  • i: ha identità
  • m: può essere spostato da

Questo mi ha portato a mettere questo diagramma alla lavagna: inserisci qui la descrizione dell'immagine

Naming

Ho osservato che avevamo solo una limitata libertà di denominazione: i due punti a sinistra (etichettati iMe i) sono ciò che le persone con più o meno formalità hanno chiamato lvaluesei due punti a destra (etichettati me Im) sono ciò che le persone con più o meno formalità hanno chiamato rvalues. Questo deve riflettersi nella nostra denominazione. Cioè, la "gamba" sinistra del Wdovrebbe avere nomi correlati lvaluee la "gamba" destra del Wdovrebbe avere nomi relativi a rvalue.I noto che tutta questa discussione / problema deriva dall'introduzione di riferimenti di valore e spostare la semantica. Queste nozioni semplicemente non esistono nel mondo di Strachey che consiste di giusto rvaluese lvalues. Qualcuno ha osservato che le idee che

  • Ognuno valueè o un lvalueo unrvalue
  • Un lvaluenon è un rvaluee un rvaluenon è unlvalue

sono profondamente radicati nella nostra coscienza, proprietà molto utili e tracce di questa dicotomia possono essere trovate in tutto il progetto di standard. Siamo tutti d'accordo sul fatto che dovremmo preservare quelle proprietà (e renderle precise). Ciò ha ulteriormente limitato le nostre scelte di denominazione. Ho osservato che gli usi formulazione della libreria standard rvalueper significare m(la generalizzazione), in modo da preservare l'aspettativa e il testo della libreria standard il punto in basso a destra del Wdeve essere denominato rvalue.

Ciò ha portato a una discussione focalizzata sulla denominazione. In primo luogo, dovevamo decidere su lvalue.Dovrebbe lvaluesignificare iMo la generalizzazione i? Guidati da Doug Gregor, abbiamo elencato i luoghi nella formulazione della lingua principale in cui la parola lvalueera qualificata per indicare l'una o l'altra. È stato creato un elenco e nella maggior parte dei casi e nel testo più complicato / fragile lvalueattualmente significa iM. Questo è il significato classico di lvalue perché "ai vecchi tempi" non si muoveva nulla; moveè una nuova nozione in C++0x. Inoltre, la denominazione del punto di inizio sinistro di W lvalueci dà la proprietà che ogni valore è un lvalueo un rvalue, ma non entrambi.

Così, la parte superiore punto di sinistra della Wè lvaluee il punto in basso a destra è rvalue.cosa vuol rendere il fondo a sinistra e punti in alto a destra? Il punto in basso a sinistra è una generalizzazione del valore classico, che consente lo spostamento. Quindi è un generalized lvalue.nome che glvalue.possiamo chiacchierare sull'abbreviazione, ma (penso) non con la logica. Abbiamo ipotizzato che in generalized lvalue qualche modo un uso serio sarebbe stato in qualche modo abbreviato, quindi è meglio farlo immediatamente (o rischiare la confusione). Il punto in alto a destra della W è meno generale di quello in basso a destra (ora, come sempre, chiamato rvalue). Quel punto rappresenta la pura nozione originale di un oggetto da cui puoi muoverti perché non può essere richiamato di nuovo (tranne che da un distruttore). Mi è piaciuta la frase specialized rvaluein contrasto con generalized lvaluemapure rvalueabbreviato per prvaluevincere (e probabilmente giustamente). Quindi, la gamba sinistra della W è lvalueed glvaluee la gamba destra è prvaluee rvalue.Incidentalmente, ogni valore è o un valore di valore o un valore, ma non entrambi.

Questo lascia il medio superiore del W: im; cioè valori che hanno identità e possono essere spostati. Non abbiamo davvero nulla che ci guidi a un buon nome per quelle bestie esoteriche. Sono importanti per le persone che lavorano con il (progetto) testo standard, ma è improbabile che diventino un nome familiare. Non abbiamo trovato alcun reale vincolo nella denominazione per guidarci, quindi abbiamo scelto 'x' per il centro, l'ignoto, lo strano, solo xpert o persino x-rated.

Steve mostra il prodotto finale


14
sì, è meglio leggere le proposte e le discussioni originali del comitato C ++, rispetto allo standard, se vuoi capire cosa significassero: D
Ivan Kush

8
I letterali non hanno identità e non possono essere spostati; sono comunque utili.
DrPizza,

Voglio solo chiarire una cosa. int && f () {return 1; } e MyClass && g () {return MyClass (); } restituisce xvalue, giusto? Quindi dove posso trovare l'identità delle espressioni f (); e "g ();"? Hanno identità, poiché esiste un'altra espressione nell'istruzione return, che fa riferimento allo stesso oggetto a cui si riferiscono: capisco bene?
Dániel Sándor,

6
@DrPizza Secondo lo standard: i letterali delle stringhe sono lvalues, tutti gli altri letterali sono prvalues. A rigor di termini potresti argomentare per dire che i letterali senza stringhe dovrebbero essere immobili, ma non è così che viene scritto lo standard.
Brian Vandenberg,

59

INTRODUZIONE

ISOC ++ 11 (ufficialmente ISO / IEC 14882: 2011) è la versione più recente dello standard del linguaggio di programmazione C ++. Contiene alcune nuove funzionalità e concetti, ad esempio:

  • riferimenti di valore
  • categorie di valori dell'espressione xvalue, glvalue, prvalue
  • spostare la semantica

Se desideriamo comprendere i concetti delle nuove categorie di valori delle espressioni, dobbiamo essere consapevoli del fatto che esistono riferimenti rvalue e lvalue. È meglio sapere che i valori possono essere passati a riferimenti a valori non costanti.

int& r_i=7; // compile error
int&& rr_i=7; // OK

Possiamo ottenere alcune intuizioni dei concetti di categorie di valori se citiamo la sottosezione intitolata Valori e valori dal progetto di lavoro N3337 (il progetto più simile allo standard ISOC ++ 11 pubblicato).

3.10 Valori e valori [basic.lval]

1 Le espressioni sono classificate in base alla tassonomia nella Figura 1.

  • Un lvalue (così chiamato, storicamente, perché i lvalues ​​potrebbero apparire sul lato sinistro di un'espressione di assegnazione) designa una funzione o un oggetto. [Esempio: se E è un'espressione di tipo puntatore, allora * E è un'espressione lvalore che si riferisce all'oggetto o alla funzione a cui E punta. Come altro esempio, il risultato della chiamata di una funzione il cui tipo restituito è un riferimento lvalue è un lvalue. —End esempio]
  • Un valore x (un valore "eXpiring") si riferisce anche a un oggetto, di solito vicino alla fine della sua vita (in modo che le sue risorse possano essere spostate, ad esempio). Un xvalue è il risultato di alcuni tipi di espressioni che coinvolgono riferimenti rvalue (8.3.2). [Esempio: il risultato della chiamata di una funzione il cui tipo restituito è un riferimento di valore è un valore x. —End esempio]
  • Un valore (valore “generalizzato”) è un valore o un valore x.
  • Un valore (così chiamato, storicamente, perché i valori possono apparire sul lato destro di un'espressione di assegnazione) è un valore x, un
    oggetto temporaneo (12.2) o un oggetto secondario o un valore che non è
    associato a un oggetto.
  • Un valore (valore "puro") è un valore che non è un valore x. [Esempio: il risultato della chiamata di una funzione il cui tipo restituito non è un
    riferimento è un valore. Anche il valore di un valore letterale come 12, 7.3e5 o
    true è un valore. —Esempio]

Ogni espressione appartiene esattamente a una delle classificazioni fondamentali in questa tassonomia: lvalue, xvalue o prvalue. Questa proprietà di un'espressione è chiamata la sua categoria di valore.

Ma non sono del tutto sicuro che questa sottosezione sia sufficiente per comprendere chiaramente i concetti, perché "di solito" non è veramente generale, "verso la fine della sua vita" non è molto concreto, "coinvolgere riferimenti di valore" non è molto chiaro, e "Esempio: il risultato della chiamata di una funzione il cui tipo restituito è un riferimento di valore è un valore x". sembra che un serpente si morda la coda.

CATEGORIE DI VALORI PRIMARI

Ogni espressione appartiene esattamente a una categoria di valore primario. Queste categorie di valori sono categorie lvalue, xvalue e prvalue.

lvalue

L'espressione E appartiene alla categoria lvalue se e solo se E si riferisce a un'entità che GIÀ ha avuto un'identità (indirizzo, nome o alias) che la rende accessibile al di fuori di E.

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.  

    i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression i in this row refers to.

    int* p_i=new int(7);
    *p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
    *p_i; // ... as the entity the expression *p_i in this row refers to.

    const int& r_I=7;
    r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
    r_I; // ... as the entity the expression r_I in this row refers to.

    f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression f() in this row refers to.

    return 0;
}

XValues

L'espressione E appartiene alla categoria xvalue se e solo se lo è

- il risultato della chiamata di una funzione, implicitamente o esplicitamente, il cui tipo restituito è un riferimento di valore al tipo di oggetto restituito, oppure

int&& f(){
    return 3;
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.

    return 0;
}

- un cast in riferimento a un valore rvalore al tipo di oggetto, oppure

int main()
{
    static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
    std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).

    return 0;
}

- un'espressione di accesso di un membro della classe che designa un membro di dati non statico di tipo non di riferimento in cui l'espressione dell'oggetto è un valore x, oppure

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.

    return 0;
}

- un'espressione da puntatore a membro in cui il primo operando è un valore x e il secondo operando è un puntatore al membro dati.

Si noti che l'effetto delle regole precedenti è che i riferimenti ai valori nominali agli oggetti sono trattati come valori e i riferimenti ai valori senza nome agli oggetti sono trattati come valori x; i riferimenti rvalue alle funzioni sono trattati come lvalues ​​con o senza nome.

#include <functional>

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
    As&& rr_a=As();
    rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
    std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.

    return 0;
}

prvalues

L'espressione E appartiene alla categoria prvalue se e solo se E non appartiene né alla categoria lvalue né alla categoria xvalue.

struct As
{
    void f(){
        this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
    }
};

As f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.

    return 0;
}

CATEGORIE DI VALORI MISTI

Esistono altre due importanti categorie di valori misti. Queste categorie di valori sono categorie rvalue e glvalue.

rvalues

L'espressione E appartiene alla categoria rvalue se e solo se E appartiene alla categoria xvalue o alla categoria prvalue.

Si noti che questa definizione significa che l'espressione E appartiene alla categoria del valore se e solo se E si riferisce a un'entità che non ha avuto alcuna identità che lo rende accessibile al di fuori di E YET.

glvalues

L'espressione E appartiene alla categoria glvalue se e solo se E appartiene alla categoria lvalue o alla categoria xvalue.

UNA REGOLA PRATICA

Scott Meyer ha pubblicato una regola empirica molto utile per distinguere i valori dai valori.

  • Se riesci a prendere l'indirizzo di un'espressione, l'espressione è un valore.
  • Se il tipo di un'espressione è un riferimento lvalue (ad esempio, T & o const T &, ecc.), Quell'espressione è un lvalue.
  • Altrimenti, l'espressione è un valore. Concettualmente (e in genere anche nei fatti), i valori corrispondono ad oggetti temporanei, come quelli restituiti da funzioni o creati attraverso conversioni di tipo implicite. La maggior parte dei valori letterali (ad es. 10 e 5.3) sono anche valori.

3
Tutti gli esempi per i valori e tutti gli esempi per i valori x sono esempi anche per i valori. Grazie per il montaggio!
Dániel Sándor,

1
Hai ragione. Le tre categorie di valori primari sono sufficienti. Neanche Rvalue è necessario. Penso che rvalue e glvalue siano nello standard per comodità.
Dániel Sándor,

1
Difficile capire che struct As{void f(){this;}}la thisvariabile è un valore. Ho pensato che thisdovrebbe essere un valore. Fino a quando lo standard 9.3.2 non dice: Nel corpo di una funzione membro non statica (9.3), la parola chiave è un'espressione di valore.
r0ng

3
@ r0ng thisè un valore ma *thisè un valore
Xeverous

1
"www" non ha sempre lo stesso indirizzo. È un valore perché è un array .
wally,

35

Le categorie di C ++ 03 sono troppo limitate per acquisire correttamente l'introduzione dei riferimenti rvalue negli attributi di espressione.

Con la loro introduzione, è stato detto che un riferimento al valore senza nome viene valutato in base a un valore, in modo tale che la risoluzione del sovraccarico preferisca i riferimenti al riferimento al valore, il che gli consentirebbe di spostare i costruttori di spostamento rispetto ai costruttori di copie. Ma è stato scoperto che ciò causa problemi ovunque, ad esempio con i tipi dinamici e con le qualifiche.

Per dimostrarlo, considera

int const&& f();

int main() {
  int &&i = f(); // disgusting!
}

Su bozze pre-xvalue, questo era permesso, perché in C ++ 03, i valori di tipi non di classe non sono mai qualificati per cv. Ma è previsto che si constapplichi nel caso di riferimento al valore rvalore, perché qui facciamo riferimento agli oggetti (= memoria!), E il rilascio di const da valori non di classe è principalmente dovuto al fatto che non vi è alcun oggetto intorno.

Il problema per i tipi dinamici è di natura simile. In C ++ 03, i valori del tipo di classe hanno un tipo dinamico noto: è il tipo statico di quell'espressione. Perché per averlo in un altro modo, hai bisogno di riferimenti o dereferenze, che valgono per un valore. Questo non è vero con riferimenti a valori senza nome, ma possono mostrare un comportamento polimorfico. Quindi per risolverlo,

  • i riferimenti al valore senza nome diventano xvalori . Possono essere qualificati e potenzialmente avere il loro tipo dinamico diverso. Preferiscono, come previsto, riferimenti di valore durante il sovraccarico e non si legano a riferimenti di valore non costante.

  • Quello che prima era un valore (valori letterali, oggetti creati da cast verso tipi non di riferimento) ora diventa un valore . Hanno la stessa preferenza di xvalues ​​durante il sovraccarico.

  • Quello che prima era un valore rimane un valore.

E vengono fatti due raggruppamenti per catturare quelli che possono essere qualificati e possono avere diversi tipi dinamici ( valori di glutei ) e quelli in cui il sovraccarico preferisce il legame di riferimento del valore ( valori ).


1
la risposta è ovviamente ragionevole. xvalue è solo rvalue che può essere qualificato in cv e digitato in modo dinamico!
ligando

26

Ho lottato con questo per molto tempo, fino a quando mi sono imbattuto nella spiegazione di cppreference.com delle categorie di valore .

In realtà è piuttosto semplice, ma trovo che sia spesso spiegato in un modo difficile da memorizzare. Qui è spiegato in modo molto schematico. Citerò alcune parti della pagina:

Categorie primarie

Le categorie di valori primari corrispondono a due proprietà delle espressioni:

  • ha identità : è possibile determinare se l'espressione si riferisce alla stessa entità di un'altra espressione, ad esempio confrontando gli indirizzi degli oggetti o le funzioni che identificano (ottenuti direttamente o indirettamente);

  • può essere spostato da : sposta costruttore, sposta operatore di assegnazione o un altro sovraccarico di funzioni che implementa sposta semantica può legarsi all'espressione.

Espressioni che:

  • hanno identità e non possono essere mossi da sono chiamate espressioni lvalue ;
  • hanno identità e possono essere mossi da sono chiamate espressioni xvalue ;
  • non hanno identità e possono essere spostati dalle espressioni prvalue ;
  • non hanno identità e non possono essere spostati da non vengono utilizzati.

lvalue

Un'espressione lvalue ("valore sinistro") è un'espressione che ha identità e da cui non è possibile spostarla .

rvalue (fino a C ++ 11), prvalue (dal C ++ 11)

Un'espressione prvalue ("pure rvalue") è un'espressione che non ha identità e da cui può essere spostata .

xValue

Un'espressione xvalue ("valore in scadenza") è un'espressione che ha identità e può essere spostata da .

glvalue

Un'espressione glvalue ("generalized lvalue") è un'espressione che è o un lvalue o un xvalue. Ha identità . Può essere spostato o meno da.

valore (dal C ++ 11)

Un'espressione rvalue ("valore corretto") è un'espressione che è o un valore o un valore x. Esso può essere spostato da . Potrebbe avere o meno identità.


1
In alcuni libri, gli xvalori mostrano che la loro x proviene da "esperto" o "eccezionale"
noɥʇʎԀʎzɐɹƆ

E, soprattutto, il loro elenco di esempi completo.
Ciro Santilli 20 冠状 病 六四 事件 法轮功

19

In che modo queste nuove categorie si relazionano con le categorie rvalue e lvalue esistenti?

Un valore C ++ 03 è ancora un valore C ++ 11, mentre un valore C ++ 03 è chiamato valore in C ++ 11.


14

Un addendum alle eccellenti risposte di cui sopra, su un punto che mi ha confuso anche dopo aver letto Stroustrup e pensato di aver compreso la distinzione valore / valore. Quando vedi

int&& a = 3,

è molto allettante leggere il int&&tipo come e concludere che aè un valore. Non è:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles

aha un nome ed è ipso facto un lvalue. Non pensare a &&come parte del tipo di a; è solo qualcosa che ti dice cosaa è permesso legarsi.

Ciò è particolarmente importante per gli T&&argomenti di tipo nei costruttori. Se scrivi

Foo::Foo(T&& _t) : t{_t} {}

copierete _tint . Hai bisogno

Foo::Foo(T&& _t) : t{std::move(_t)} {}se vuoi muoverti. Il mio compilatore mi avrebbe avvisato quando avrei lasciato fuori il move!


1
Penso che questa risposta possa essere chiarita. "A cosa aè consentito legare": Certo, ma nelle righe 2 e 3 le tue variabili sono c & b, e non è una cosa a cui si lega, e il tipo di anon è rilevante qui, vero? Le linee sarebbero le stesse se afosse dichiarata int a. La differenza principale effettiva qui è che nella riga 1 a non deve essere const
associato

12

Dato che le risposte precedenti coprivano in modo esauriente la teoria alla base delle categorie di valori, c'è solo un'altra cosa che vorrei aggiungere: puoi davvero giocarci e testarlo.

Per alcune sperimentazioni pratiche con le categorie di valori, è possibile utilizzare l' identificatore di declinazione . Il suo comportamento distingue esplicitamente tra le tre categorie di valori primari (xvalue, lvalue e prvalue).

L'uso del preprocessore ci fa risparmiare un po 'di battitura ...

Categorie primarie:

#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value

Categorie miste:

#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))
#define IS_RVALUE(X) (IS_PRVALUE(X) || IS_XVALUE(X))

Ora possiamo riprodurre (quasi) tutti gli esempi da cppreference sulla categoria di valore .

Ecco alcuni esempi con C ++ 17 (per terse static_assert):

void doesNothing(){}
struct S
{
    int x{0};
};
int x = 1;
int y = 2;
S s;

static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));

static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));

static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc 9.1. Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

Le categorie miste sono un po 'noiose dopo aver capito la categoria principale.

Per alcuni altri esempi (e sperimentazione), dai un'occhiata al seguente link su Compiler Explorer . Non preoccuparti di leggere l'assemblea, però. Ho aggiunto molti compilatori solo per assicurarmi che funzioni su tutti i compilatori comuni.


Penso che in #define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)realtà dovrei #define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))guardare altrimenti cosa succede se voi &&due IS_GLVALUE.
Gabriel Devillers,
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.