Co to są wartości, wartości lvalu, wartości x, wartości glvalu i prvalues?

1356

W C ++ 03 wyrażenie to albo wartość, albo wartość .

W C ++ 11 wyrażenie może być:

  1. wartość r
  2. wartość
  3. xvalue
  4. glvalue
  5. prvalue

Dwie kategorie stały się pięcioma kategoriami.

  • Jakie są te nowe kategorie wyrażeń?
  • Jak te nowe kategorie odnoszą się do istniejących kategorii wartości i wartości?
  • Czy kategorie wartości i wartości w C ++ 0x są takie same jak w C ++ 03?
  • Dlaczego te nowe kategorie są potrzebne? Czy bogowie WG21 tylko próbują wprowadzić w błąd zwykłych śmiertelników?
James McNellis
źródło
9
@Pilil Potter: In C ++ 03? Tak. Wartość może być używana jako wartość, ponieważ istnieje standardowa konwersja wartości z wartości na wartość.
James McNellis,
14
@Tyler: „Jeśli możesz to przypisać, jest to wartość, w przeciwnym razie jest to wartość”. -> Źle, można przypisać do rvalues klasy: string("hello") = string("world").
fredoverflow
4
Pamiętaj, że jest to kategoria wartości. Wyrażenia mogą mieć więcej właściwości. Należą do nich pole bitowe (prawda / fałsz), tymczasowe (prawda / fałsz) i typ (jego typ).
Johannes Schaub - litb
30
Myślę, że powyższy link Freda jest lepszy niż którakolwiek z odpowiedzi tutaj. Link nie działa. Został przeniesiony do: stroustrup.com/terminology.pdf
R. Martinho Fernandes
74
w C ++ nawet twoje typy mają typy
nielsbot

Odpowiedzi:

634

Myślę, że ten dokument może służyć jako niezbyt krótkie wprowadzenie: n3055

Cała masakra rozpoczęła się od semantyki ruchu. Gdy mamy już wyrażenia, które można przenosić, a nie kopiować, nagle łatwe do uchwycenia reguły wymagały rozróżnienia między wyrażeniami, które można przenosić, i w jakim kierunku.

Z tego, co sądzę na podstawie szkicu, rozróżnienie wartości r / l pozostaje takie samo, tylko w kontekście przenoszenia rzeczy robi się bałagan.

Czy są potrzebne? Prawdopodobnie nie, jeśli chcemy utracić nowe funkcje. Ale aby umożliwić lepszą optymalizację, prawdopodobnie powinniśmy je przyjąć.

Cytując n3055 :

  • Lwartość (tzw historycznie lwartościami ponieważ może pojawić się po lewej stronie wyrażenia przypisania) oznacza funkcję lub obiektu. [Przykład: Jeśli Ejest wyrażeniem typu wskaźnikowego, to *E jest to wyrażenie wartości odnoszące się do obiektu lub funkcji, do której E wskazuje. Jako kolejny przykład wynikiem wywołania funkcji, której typem zwracanym jest odwołanie do wartości, jest wartość.]
  • Xvalue (e „WYGASAJĄCEJ” wartość) również odnosi się do przedmiotu, zwykle w pobliżu końca swojej żywotności (tak, że zasoby może być, na przykład). Wartość x jest wynikiem pewnych rodzajów wyrażeń zawierających odwołania do wartości. [Przykład: wynikiem wywołania funkcji, której typem zwracanym jest odwołanie do wartości, jest wartość x.]
  • Glvalue ( „uogólnionego” lwartość) jest lwartość lub xvalue .
  • RValue (tzw historycznie, ponieważ rvalues może pojawić się na prawej stronie wyrażenia przypisania) jest xvalue, tymczasowy obiekt lub podobiekt tego, czy wartość, która nie jest związana z obiektem.
  • Prvalue ( „czysty” RValue) jest RValue nie jest xvalue. [Przykład: Wywołanie funkcji, której typ zwracany nie jest odwołaniem, jest wartością]

Dokument, o którym mowa, stanowi świetne odniesienie do tego pytania, ponieważ pokazuje dokładne zmiany standardu, które nastąpiły w wyniku wprowadzenia nowej nomenklatury.

Kornel Kisielewicz
źródło
Dzięki, ta odpowiedź jest naprawdę pomocna! Ale mój kompilator nie zgadza się z twoimi przykładami dla wartości i wartości; są dokładnie przeciwnie. Zwracanie przez odwołanie do wartości daje mi wartość, a zwracanie przez wartość daje mi wartość x. Czy je pomieszałeś, czy moje łóżko testowe jest zepsute? Próbowałem tego z GCC 4.6.1, clang (z svn) i MSVC, i wszystkie wykazują takie samo zachowanie.
Kim Gräsman,
Ups, właśnie podążyłem za linkiem i zauważyłem, że przykłady są w źródle. Pójdę znaleźć kopię tego standardu i sprawdzę, co mówi ...
Kim Gräsman,
4
Używam makr stąd, aby przetestować różne wyrażenia: stackoverflow.com/a/6114546/96963 Możliwe, że źle je zdiagnozowali.
Kim Gräsman,
1
Dodanie wartości x nie dotyczy semantyki przenoszenia. Tylko w przypadku zarówno wartości lvalue, jak i rvalue semantyka move, doskonałe referencje do przodu i do rvalue nadal działają dobrze. Myślę, że xvalue jest tylko dla operatora decltype: jeśli wyrażenie operandu to xvalue, decltype podaje typ odwołania do rvalue.
ligand
1
@MuhamedCicak „Każde wyrażenie jest albo wartością, albo wartością”: to prawda; a standard (lub dokument n3055) nie mówi, że jest fałszywy. To zdanie zostało przekreślone dlatego, że patrzyłeś na zmiany między dwiema wersjami dokumentu. Zdanie zostało usunięte, ponieważ stało się zbędne po dodaniu dokładniejszych wyjaśnień.
maks.
337

Jakie są te nowe kategorie wyrażeń?

FCD (n3092) ma doskonały opis:

- Wartość (tak zwana historycznie, ponieważ wartości po lewej stronie wyrażenia przypisania) mogą oznaczać funkcję lub obiekt. [Przykład: jeśli E jest wyrażeniem typu wskaźnikowego, to * E jest wyrażeniem wartościowym odnoszącym się do obiektu lub funkcji, na które wskazuje E. Jako kolejny przykład wynikiem wywołania funkcji, której typem zwracanym jest odwołanie do wartości, jest wartość. —Przykład]

- Wartość x (wartość „eXpiring”) odnosi się również do obiektu, zwykle pod koniec jego życia (na przykład, aby można było przenieść jego zasoby). Wartość x jest wynikiem pewnych rodzajów wyrażeń obejmujących odwołania do wartości (8.3.2). [Przykład: wynikiem wywołania funkcji, której typem zwracanym jest odwołanie do wartości jest wartość x. —Przykład]

- Glvalue (wartość „uogólniona”) jest wartością lub wartością x.

- Wartość (tak zwana historycznie, ponieważ wartości mogą pojawiać się po prawej stronie wyrażeń przypisania) jest wartością x, obiektem tymczasowym (12.2) lub jej podobiektem lub wartością, która nie jest powiązana z obiektem.

- Wartość pr (wartość „czysta”) to wartość, która nie jest wartością x. [Przykład: Wywołanie funkcji, której typem zwracanym nie jest odwołanie, jest prvalue. Wartość literału, takiego jak 12, 7,3e5, lub prawda, jest również wartością wstępną. —Przykład]

Każde wyrażenie należy do dokładnie jednej z podstawowych klasyfikacji tej taksonomii: lvalue, xvalue lub prvalue. Ta właściwość wyrażenia nazywana jest jego kategorią wartości. [Uwaga: Omówienie każdego wbudowanego operatora w klauzuli 5 wskazuje kategorię wartości, którą daje, i kategorie wartości operandów, których oczekuje. Na przykład, wbudowane operatory przypisania oczekują, że lewy operand jest wartością, a prawy operand jest wartością, w wyniku czego otrzyma wartość. Operatory zdefiniowane przez użytkownika są funkcjami, a kategorie wartości, których spodziewają się i uzyskują, zależą od ich parametrów i typów zwracanych. —Wskazówka

Proponuję jednak przeczytać całą sekcję 3.10 Wartości i wartości lv .

Jak te nowe kategorie odnoszą się do istniejących kategorii wartości i wartości?

Jeszcze raz:

Taksonomia

Czy kategorie wartości i wartości w C ++ 0x są takie same jak w C ++ 03?

Semantyka wartości r ewoluowała szczególnie wraz z wprowadzeniem semantyki ruchu.

Dlaczego te nowe kategorie są potrzebne?

Aby konstrukcja / przypisanie ruchów mogło zostać zdefiniowane i obsługiwane.

bezpośrednio
źródło
54
Podoba mi się tutaj schemat. Myślę, że warto zacząć od odpowiedzi: „Każde wyrażenie należy do dokładnie jednej z podstawowych klasyfikacji tej taksonomii: lvalue, xvalue lub prvalue”. Następnie łatwo jest użyć diagramu, aby pokazać, że te trzy podstawowe klasy są połączone w celu uzyskania wartości glvalue i rvalue.
Aaron McDaid,
2
„jest glvalue” jest równoważne z „nie jest prvalue”, a „is rvalue” oznacza „nie jest lvalue”.
Vladimir Reshetnikov
2
Ten pomógł mi najbardziej: bajamircea.github.io/assets/2016-04-07-move-forward/… (schemat Venna kategorii wartości)
John P
1
@AaronMcDaid Cześć, szybkie pytanie, czy ty / ktoś może odpowiedzieć ... Dlaczego nie nazwać glvaluetak jak lvaluei lvaluejak plvalue, aby zachować spójność?
Vijay Chavda,
184

Zacznę od twojego ostatniego pytania:

Dlaczego te nowe kategorie są potrzebne?

Standard C ++ zawiera wiele reguł dotyczących kategorii wartości wyrażenia. Niektóre reguły wprowadzają rozróżnienie między wartością lvalue a rvalue. Na przykład, jeśli chodzi o rozdzielczość przeciążenia. Inne zasady wprowadzają rozróżnienie między wartością glvalue a prvalue. Na przykład możesz mieć wartość glvalue z niekompletnym lub abstrakcyjnym typem, ale nie ma wartości z niekompletnym lub abstrakcyjnym typem. Zanim mieliśmy tę terminologię, reguły, które faktycznie muszą odróżniać glvalue / prvalue, odnosiły się do lvalue / rvalue i były albo niezamierzone, albo zawierały wiele wyjaśnień i wyjątków od reguły a la „... chyba że wartość jest spowodowana nienazwanym odniesienie do wartości ... ”. Wydaje się więc, że dobrym pomysłem jest nadanie pojęciom glvalues ​​i prvalues ​​własnej nazwy.

Jakie są te nowe kategorie wyrażeń? Jak te nowe kategorie odnoszą się do istniejących kategorii wartości i wartości?

Nadal mamy warunki lvalue i rvalue, które są kompatybilne z C ++ 98. Właśnie podzieliliśmy wartości na dwie podgrupy, wartości i wartości, a wartości lvalu i xvale nazywamy wartościami glvalu. Xvalues ​​to nowy rodzaj kategorii wartości dla nienazwanych odwołań do wartości. Każde wyrażenie jest jednym z tych trzech: lvalue, xvalue, prvalue. Diagram Venna wyglądałby tak:

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

Przykłady z funkcjami:

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

Ale nie zapominaj też, że nazwane referencje wartości są wartościami lv:

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}
sellibitze
źródło
165

Dlaczego te nowe kategorie są potrzebne? Czy bogowie WG21 tylko próbują wprowadzić w błąd zwykłych śmiertelników?

Nie sądzę, że inne odpowiedzi (dobre, choć wiele z nich jest) naprawdę zawierają odpowiedź na to konkretne pytanie. Tak, te kategorie i takie istnieją, aby umożliwić semantykę przenoszenia, ale złożoność istnieje z jednego powodu. Jest to jedna nienaruszalna zasada przenoszenia rzeczy w C ++ 11:

Poruszasz się tylko wtedy, gdy jest to bez wątpienia bezpieczne.

Właśnie dlatego te kategorie istnieją: aby móc mówić o wartościach, w których można bezpiecznie od nich przejść, i mówić o wartościach, w których nie ma.

W najwcześniejszej wersji referencji o wartości r ruch odbywał się łatwo. Zbyt łatwo. Łatwo było, że istniało wiele możliwości niejawnego przenoszenia rzeczy, gdy użytkownik tak naprawdę nie chciał.

Oto okoliczności, w których można bezpiecznie coś przenieść:

  1. Gdy jest to tymczasowe lub jego podobiektyw. (wartość)
  2. Gdy użytkownik wyraźnie powiedział, aby go przenieść .

Jeśli to zrobisz:

SomeType &&Func() { ... }

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

Co to robi? W starszych wersjach specyfikacji, zanim pojawiło się 5 wartości, sprowokowałoby to ruch. Oczywiście, że tak. Przekazałeś odwołanie do wartości do konstruktora, a zatem wiąże się ono z konstruktorem, który pobiera odwołanie do wartości. To oczywiste.

Jest z tym tylko jeden problem; nie prosiłeś go o przeniesienie. Och, możesz powiedzieć, że &&powinna to być wskazówka, ale to nie zmienia faktu, że złamała zasadę. valnie jest tymczasowy, ponieważ tymczasowe nie mają nazw. Być może przedłużyłeś czas życia tymczasowego, ale to oznacza, że ​​nie jest tymczasowy ; jest jak każda inna zmienna stosu.

Jeśli nie jest to tymczasowe i nie poprosiłeś o jego przeniesienie, wówczas poruszanie się jest złe.

Oczywistym rozwiązaniem jest zrobienie valwartości. Oznacza to, że nie możesz się z niego ruszać. Ok dobrze; nazywa się, więc jest to wartość.

Gdy to zrobisz, nie będziesz już mógł powiedzieć, że SomeType&&oznacza to to samo wszędzie. Wprowadzono teraz rozróżnienie między nazwanymi referencjami wartości i nienazwanymi referencjami wartości. Nazwane odniesienia wartości są wartościami lv; to było nasze rozwiązanie powyżej. Co więc nazywamy referencjami wartości bez nazwy (wartość zwracana z Funcgóry)?

To nie jest wartość, ponieważ nie można przejść od wartości. A my potrzebujemy , aby móc przejść przez przekazujących &&; jak inaczej mógłbyś wyraźnie powiedzieć, żeby coś przenieść? W std::movekońcu to właśnie powraca. To nie jest wartość (w starym stylu), ponieważ może być po lewej stronie równania (rzeczy są w rzeczywistości nieco bardziej skomplikowane, zobacz to pytanie i komentarze poniżej). To nie jest ani wartość, ani wartość; to nowy rodzaj rzeczy.

To, co mamy, to wartość, którą można traktować jako wartość, zróżnicą, że można ją w sposób niejawny przenosić. Nazywamy to xwartością.

Zauważ, że wartości x są tym, co sprawia, że ​​zdobywamy pozostałe dwie kategorie wartości:

  • Wartość jest tak naprawdę nową nazwą dla poprzedniego typu wartości, tzn. Są to wartości, które nie są wartościami.

  • Glvalues ​​to połączenie wartości xvalues ​​i lvalues ​​w jednej grupie, ponieważ mają one wiele wspólnych właściwości.

Tak naprawdę wszystko sprowadza się do wartości i potrzeby ograniczenia ruchu do dokładnie i tylko niektórych miejsc. Miejsca te są zdefiniowane według kategorii wartości; wartości są niejawnymi ruchami, a wartości są jawnymi ruchami ( std::movezwraca wartość x).

Nicol Bolas
źródło
11
@Thomas: To przykład; nie ma znaczenia, w jaki sposób tworzy wartość zwracaną. Liczy się to, że zwraca &&.
Nicol Bolas,
1
Uwaga: wartości mogą znajdować się po lewej stronie równania, również - jak w X foo(); foo() = X;... Z tego podstawowego powodu nie mogę do końca podążać za powyższą doskonałą odpowiedzią, ponieważ tak naprawdę rozróżniasz tylko nowa xvalue i prvalue w starym stylu, oparte na fakcie, że może być na lhs.
Dan Nissenbaum,
1
Xbycie klasą; X foo();bycie deklaracją funkcji i foo() = X();bycie wierszem kodu. ( foo() = X();W moim powyższym komentarzu pominąłem drugi zestaw nawiasów .) Pytanie, które właśnie wysłałem z zaznaczonym użyciem, znajduje się na stackoverflow.com/questions/15482508/...
Dan Nissenbaum,
1
@ DanNissenbaum „xvalue nie może znajdować się po lewej stronie wyrażenia przypisania” - dlaczego nie? Zobacz ideone.com/wyrxiT
Michaił
1
Oświecająca odpowiedź. To bez wątpienia najlepsza odpowiedź tutaj. Dało mi to uzasadnienie wprowadzenia nowych kategorii wartości i tego, co wydarzyło się wcześniej.
Nikos
136

IMHO, najlepsze wyjaśnienie jego znaczenia dało nam Stroustrup + wziął pod uwagę przykłady Dániela Sándora i Mohana :

Stroustrup:

Teraz bardzo się martwiłem. Najwyraźniej zmierzaliśmy w kierunku impasu, bałaganu lub obu naraz. Spędziłem porę lunchu, analizując, które właściwości (wartości) były niezależne. Były tylko dwie niezależne właściwości:

  • has identity - tj. I adres, wskaźnik, użytkownik może ustalić, czy dwie kopie są identyczne itp.
  • can be moved from - tzn. Możemy pozostawić źródło „kopii” w jakimś nieokreślonym, ale poprawnym stanie

Doprowadziło mnie to do wniosku, że istnieją dokładnie trzy rodzaje wartości (użycie wyrażenia regularnego wyrażenia regularnego polegającego na użyciu dużej litery do wskazania wartości ujemnej - spieszyłem się):

  • iM: ma tożsamość i nie można go przenieść
  • im: ma tożsamość i można go przenieść (np. w wyniku rzutowania wartości na odwołanie do wartości)
  • Im: nie ma tożsamości i można go przenieść.

    Czwarta możliwość, IM(nie ma tożsamości i nie można jej przenieść) nie jest przydatna w C++(lub, jak sądzę) w żadnym innym języku.

Oprócz tych trzech podstawowych klasyfikacji wartości mamy dwa oczywiste uogólnienia, które odpowiadają dwóm niezależnym właściwościom:

  • i: ma tożsamość
  • m: można przenieść z

Doprowadziło mnie to do umieszczenia tego schematu na tablicy: wprowadź opis zdjęcia tutaj

Nazewnictwo

Zauważyłem, że mieliśmy ograniczoną swobodę nazewnictwa: dwa punkty po lewej stronie (oznaczone iMi i) nazywają to, co nazywają osoby o mniej lub bardziej formalności, lvaluesa dwa punkty po prawej stronie (oznaczone mi Im) to, co ludzie o mniej lub bardziej formalności zadzwoniłem rvalues. Musi to znaleźć odzwierciedlenie w naszej nazwie. Oznacza to, że lewa „noga” Wpowinna mieć nazwy powiązane z, lvaluea prawa „noga” Wpowinna mieć nazwy związane z. rvalue.Zauważam, że cała ta dyskusja / problem wynika z wprowadzenia odniesień do wartości i semantyki ruchu. Te pojęcia po prostu nie istnieją w świecie Stracheya, w skład którego wchodzą sprawiedliwi rvaluesi lvalues. Ktoś zauważył, że pomysły, które

  • Każdy valuejest albo lvalueanrvalue
  • An lvaluenie jest an rvaluei an rvaluenie jest anlvalue

są głęboko zakorzenione w naszej świadomości, bardzo przydatne właściwości, a ślady tej dychotomii można znaleźć w całym projekcie standardu. Wszyscy zgodziliśmy się, że powinniśmy zachować te właściwości (i sprecyzować je). To dodatkowo ograniczyło nasze możliwości nazewnictwa. Zauważyłem, że standardowe sformułowanie w bibliotece rvalueoznacza m(uogólnienie), aby zachować oczekiwania i tekst standardowej biblioteki, Wnależy nazwać prawy dolny punktrvalue.

Doprowadziło to do skoncentrowanej dyskusji na temat nazewnictwa. Po pierwsze, musieliśmy zdecydować, lvalue.czy powinno to lvalueoznaczać iMczy uogólnienie i? Pod kierownictwem Douga Gregora wymieniliśmy miejsca w podstawowych sformułowaniach językowych, w których słowo lvalueto kwalifikowało się do oznaczenia jednego lub drugiego. Sporządzono listę, w większości przypadków iw najbardziej trudnym / kruchym tekście, jaki lvalueobecnie oznacza iM. Jest to klasyczne znaczenie wartości, ponieważ „w dawnych czasach” nic nie zostało przeniesione; movejest nowatorskim pojęciem w C++0x. Ponadto, nazwanie górnego lewego punktu W lvaluedaje nam właściwość, że każda wartość jest lvaluelub nie rvalue, ale nie obie.

Tak więc lewy górny punkt Wjest, lvaluea prawy dolny punkt rvalue.Co sprawia, że ​​dolny lewy i prawy górny punkt? Dolny lewy punkt jest uogólnieniem klasycznej wartości, pozwalającym na ruch. Więc jest to generalized lvalue.nazwaliśmy go glvalue.Możesz spierać się o skrót, ale (myślę) nie z logiką. Zakładaliśmy, że w poważnym użyciu i generalized lvalue tak zostanie skrócone, więc lepiej zrób to natychmiast (lub ryzykuj zamieszanie). Prawy górny punkt W jest mniej ogólny niż prawy dolny (teraz, jak zawsze, nazywany rvalue). Ten punkt reprezentuje pierwotne czyste pojęcie obiektu, z którego można się poruszać, ponieważ nie można do niego ponownie się odwoływać (z wyjątkiem destruktora). Podobało mi się to zdanie specialized rvaluew przeciwieństwie do generalized lvaluealepure rvaluew skrócie prvaluewygrał (i prawdopodobnie słusznie). Tak więc lewa noga W jest, lvaluea glvalueprawa noga prvaluei rvalue.Nawiasem mówiąc, każda wartość jest albo wartością glvalue, albo prvalue, ale nie obydwoma.

Pozostawia to top środku W: im; to znaczy wartości, które mają tożsamość i mogą być przenoszone. Naprawdę nie mamy niczego, co prowadzi nas do dobrego imienia dla tych ezoterycznych bestii. Są ważne dla osób pracujących z (szkicowym) standardowym tekstem, ale jest mało prawdopodobne, aby stały się nazwiskiem domowym. Nie znaleźliśmy żadnych prawdziwych ograniczeń w nazewnictwie, które mogłyby nas poprowadzić, dlatego wybraliśmy „x” dla centrum, nieznanego, dziwnego, tylko xperta, a nawet x-oceny.

Steve popisuje się produktem końcowym

Ivan Kush
źródło
14
tak, lepiej jest przeczytać oryginalne propozycje i dyskusje komitetu C ++ niż standardowy, jeśli chcesz zrozumieć, co mieli na myśli: D
Ivan Kush
8
Literały nie mają tożsamości i nie można ich przenieść; są jednak przydatne.
DrPizza
Chcę tylko coś wyjaśnić. int && f () {return 1; } i MyClass && g () {return MyClass (); } zwróć wartość x, prawda? Gdzie więc mogę znaleźć tożsamość wyrażeń f (); i „g ();”? Mają tożsamość, ponieważ w instrukcji return znajduje się inne wyrażenie, które odnosi się do tego samego obiektu, do którego się odnoszą - czy dobrze to rozumiem?
Dániel Sándor
6
@DrPizza Zgodnie ze standardem: literały łańcuchowe to lvalues, wszystkie inne literały to prvalues. Ściśle mówiąc, można argumentować, że literały nie łańcuchowe powinny być nieruchome, ale nie w taki sposób zapisuje się standard.
Brian Vandenberg
59

WPROWADZENIE

ISOC ++ 11 (oficjalnie ISO / IEC 14882: 2011) to najnowsza wersja standardu języka programowania C ++. Zawiera kilka nowych funkcji i pojęć, na przykład:

  • referencje wartości
  • kategorie wartości wyrażeń xvalue, glvalue, prvalue
  • przenieść semantykę

Jeśli chcielibyśmy zrozumieć koncepcje nowych kategorii wartości wyrażeń, musimy zdawać sobie sprawę z tego, że istnieją odwołania do wartości i wartości. Lepiej jest wiedzieć, że wartości można przekazywać do odwołań do wartości nie stałych.

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

Zyskamy trochę intuicji pojęć kategorii wartości, cytując podrozdział zatytułowany „Wartości lvalu i wartości rvalu” z roboczego szkicu N3337 (najbardziej podobny szkic do opublikowanego standardu ISOC ++ 11).

3.10 Wartości lv i wartości rv [basic.lval]

1 Wyrażenia są podzielone na kategorie zgodnie z taksonomią na rycinie 1.

  • Wartość (tak zwana historycznie, ponieważ wartości mogą pojawiać się po lewej stronie wyrażenia przypisania) oznacza funkcję lub obiekt. [Przykład: jeśli E jest wyrażeniem typu wskaźnikowego, to * E jest wyrażeniem wartościowym odnoszącym się do obiektu lub funkcji, na które wskazuje E. Jako kolejny przykład wynikiem wywołania funkcji, której typem zwracanym jest odwołanie do wartości, jest wartość. —Przykład]
  • Wartość x (wartość „eXpiring”) odnosi się również do obiektu, zwykle pod koniec jego życia (na przykład, aby można było przenieść jego zasoby). Wartość x jest wynikiem pewnych rodzajów wyrażeń obejmujących odwołania do wartości (8.3.2). [Przykład: wynikiem wywołania funkcji, której typem zwracanym jest odwołanie do wartości jest wartość x. —Przykład]
  • Glvalue (wartość „uogólniona”) to wartość lub wartość x.
  • Wartość (tak zwana historycznie, ponieważ wartości mogą pojawiać się po prawej stronie wyrażenia przypisania) jest wartością x, obiektem
    tymczasowym (12.2) lub jej podobiektem lub wartością, która nie jest
    powiązana z obiektem.
  • Prvalue („czysta” wartość) to wartość, która nie jest wartością x. [Przykład: Wywołanie funkcji, której typem zwracanym nie jest
    odwołanie, jest prvalue. Wartość literału, takiego jak 12, 7,3e5, lub
    prawda, jest również wartością wstępną. —Przykład]

Każde wyrażenie należy do dokładnie jednej z podstawowych klasyfikacji tej taksonomii: lvalue, xvalue lub prvalue. Ta właściwość wyrażenia nazywana jest jego kategorią wartości.

Nie jestem jednak pewien, czy ten podrozdział wystarcza do jasnego zrozumienia pojęć, ponieważ „zwykle” nie jest tak naprawdę ogólny, „pod koniec swojego życia” nie jest tak naprawdę konkretny, „uwzględnianie odniesień do wartości” nie jest do końca jasne, i „Przykład: Wynikiem wywołania funkcji, której typem zwracanym jest odwołanie do wartości, jest wartość x.” brzmi jak wąż gryzie ogonem.

KATEGORIE WARTOŚCI PODSTAWOWEJ

Każde wyrażenie należy do dokładnie jednej podstawowej kategorii wartości. Te kategorie wartości to kategorie lvalue, xvalue i prvalue.

wartości

Wyrażenie E należy do kategorii wartości tylko i tylko wtedy, gdy E odnosi się do jednostki, która JUŻ Miała tożsamość (adres, nazwę lub alias), która umożliwia jej dostęp poza 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;
}

wartości

Wyrażenie E należy do kategorii xvalue tylko wtedy, gdy jest

- wynik wywołania funkcji, niejawnie lub jawnie, której typem zwracanym jest odwołanie do wartości typu zwracanego obiektu, lub

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;
}

- rzutowanie na odwołanie wartości do typu obiektu, lub

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;
}

- wyrażenie dostępu do elementu klasy oznaczające element danych niestatycznych typu innego niż referencyjny, w którym wyrażenie obiektowe jest wartością x, lub

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;
}

- wyrażenie wskaźnik do elementu, w którym pierwszy operand jest wartością x, a drugi operand jest wskaźnikiem do elementu danych.

Zauważ, że efekt powyższych reguł jest taki, że nazwane odwołania wartości do obiektów są traktowane jako wartości lv, a nienazwane odniesienia wartości do obiektów są traktowane jak wartości x; referencje wartości do funkcji są traktowane jako wartości lv, niezależnie od tego, czy są nazwane czy nie.

#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;
}

wartości

Wyrażenie E należy do kategorii prvalue wtedy i tylko wtedy, gdy E nie należy ani do wartości lvalue, ani do kategorii 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;
}

KATEGORIE MIESZANEJ WARTOŚCI

Istnieją dwie dalsze ważne kategorie mieszanych wartości. Te kategorie wartości to kategorie wartości i wartości glvalue.

wartości

Wyrażenie E należy do kategorii wartości, tylko jeśli E należy do kategorii wartości lub do kategorii wartości.

Zauważ, że ta definicja oznacza, że ​​wyrażenie E należy do kategorii wartości, tylko wtedy, gdy E odnosi się do encji, która nie miała żadnej tożsamości, która czyni ją dostępną poza E YET.

wartości glvalues

Wyrażenie E należy do kategorii glvalue wtedy i tylko wtedy, gdy E należy do kategorii lvalue lub do kategorii xvalue.

PRAKTYCZNA ZASADA

Scott Meyer opublikował bardzo przydatną ogólną zasadę odróżniającą wartości od wartości lv.

  • Jeśli możesz wziąć adres wyrażenia, wyrażenie jest wartością.
  • Jeśli typem wyrażenia jest odwołanie do wartości (np. T & lub const T & itp.), To wyrażenie jest wartością.
  • W przeciwnym razie wyrażenie jest wartością. Pod względem koncepcyjnym (i zazwyczaj także w rzeczywistości) wartości rv odpowiadają obiektom tymczasowym, takim jak te zwracane z funkcji lub tworzone przez konwersje typu niejawnego. Większość wartości literalnych (np. 10 i 5,3) to również wartości.
Dániel Sándor
źródło
3
Wszystkie przykłady wartości lv i wszystkie wartości xvalues ​​są również przykładami dla wartości glvalu. Dziękujemy za edycję!
Dániel Sándor
1
Masz rację. Wystarczą trzy podstawowe kategorie wartości. Wartość nie jest również konieczna. Myślę, że wartość rvalue i glvalue są standardem dla wygody.
Dániel Sándor
1
Trudno było zrozumieć, struct As{void f(){this;}}że thiszmienna jest prvalue. Myślałem, że thispowinna być wartość. Dopóki standard 9.3.2 nie mówi: W treści niestatystycznej (9,3) funkcji składowej słowo kluczowe jest wyrażeniem prvalue.
od
3
@ r0ng thisjest prvalue ale *thisjest lwartością
Xeverous
1
"www" nie zawsze ma ten sam adres. Jest to wartość, ponieważ jest tablicą .
wally
35

Kategorie C ++ 03 są zbyt ograniczone, aby poprawnie wprowadzić wprowadzenie referencji wartości do atrybutów wyrażeń.

Wraz z ich wprowadzeniem powiedziano, że nienazwane odwołanie do wartości ocenia się na wartość taką, że rozdzielczość przeciążenia wolałaby powiązania odniesienia do wartości, co spowodowałoby, że wybrałby konstruktor ruchu zamiast konstruktora kopiowania. Okazało się jednak, że powoduje to problemy, na przykład w przypadku typów dynamicznych i kwalifikacji.

Aby to pokazać, zastanów się

int const&& f();

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

W wersjach roboczych przed wartością x było to dozwolone, ponieważ w C ++ 03 wartości różnych typów nie są nigdy kwalifikowane do cv. Ale to ma oznaczać, że constma zastosowanie w przypadku RValue-odniesienia, bo tu nie odnosi się do obiektów (= pamięć!), I upuszczanie const z rvalues non-klasa jest głównie z tego powodu, że nie ma żadnego obiektu dookoła.

Problem z typami dynamicznymi ma podobny charakter. W C ++ 03 wartości rv typu klasy mają znany typ dynamiczny - jest to typ statyczny tego wyrażenia. Ponieważ, aby mieć inny sposób, potrzebujesz referencji lub dereferencji, które oceniają na wartość. Nie jest to prawdą w przypadku nienazwanych odwołań do wartości, ale mogą one wykazywać zachowanie polimorficzne. Aby to rozwiązać,

  • nienazwane odwołania do wartości stają się wartościami x . Mogą być zakwalifikowani i potencjalnie mogą mieć inny typ dynamiczny. Robią, tak jak zamierzają, wolą odwołania do wartości podczas przeciążania i nie będą wiązać się z nie-stałymi odwołaniami do wartości.

  • To, co poprzednio było wartością (literały, obiekty tworzone przez rzutowanie na typy niebędące odniesieniami), teraz staje się wartością prywatną . Mają takie same preferencje jak wartości x podczas przeciążania.

  • To, co poprzednio było wartością, pozostaje wartością.

Wykonano dwie grupy, aby uchwycić te, które można zakwalifikować i mogą mieć różne typy dynamiczne ( glvalues ) oraz te, w których przeciążenie woli wiązanie odniesienia wartości (wartość rvalues ).

Johannes Schaub - litb
źródło
1
odpowiedź jest oczywiście rozsądna. xvalue to tylko wartość, którą można zakwalifikować do cv i pisać dynamicznie!
ligand
26

Walczyłem z tym przez długi czas, dopóki nie natknąłem się na wyjaśnienie kategorii wartości cppreference.com .

W rzeczywistości jest to dość proste, ale uważam, że często wyjaśnia się je w sposób trudny do zapamiętania. Tutaj wyjaśniono to bardzo schematycznie. Zacytuję niektóre części strony:

Główne kategorie

Główne kategorie wartości odpowiadają dwóm właściwościom wyrażeń:

  • ma tożsamość : możliwe jest ustalenie, czy wyrażenie odnosi się do tej samej jednostki, co inne wyrażenie, na przykład poprzez porównanie adresów obiektów lub funkcji, które identyfikują (uzyskane bezpośrednio lub pośrednio);

  • można przenieść z : konstruktora ruchu, operatora przypisania ruchu lub innego przeciążenia funkcji, które implementuje semantykę przenoszenia może wiązać się z wyrażeniem.

Wyrażenia, które:

  • mają tożsamość i nie można ich przenieść, nazywane są wyrażeniami lvalue ;
  • mają tożsamość i mogą być przenoszone, nazywane są wyrażeniami xvalue ;
  • nie mają tożsamości i można się z nich przenieść, nazywane są wyrażeniami prvalue ;
  • nie mają tożsamości i nie można ich przenieść, nie są używane.

wartość

Wyrażenie lvalue („wartość lewa”) to wyrażenie, które ma tożsamość i nie można go przenieść .

rvalue (do C ++ 11), prvalue (od C ++ 11)

Wyrażenie prvalue („czysta rvalue”) jest wyrażeniem, które nie ma tożsamości i można je przenieść .

xvalue

Wyrażenie xvalue („wartość wygasająca”) to wyrażenie, które ma tożsamość i można je przenieść .

glvalue

Wyrażenie glvalue („wartość uogólniona”) to wyrażenie, które jest albo wartością lvalue, albo xvalue. To ma tożsamości . Można go przenieść lub nie.

wartość (od C ++ 11)

Wyrażenie rvalue („właściwa wartość”) to wyrażenie, które jest albo wartością prvalue, albo xvalue. To może być przeniesiony z . Może, ale nie musi, mieć tożsamość.

Felix Dombek
źródło
1
W niektórych książkach wartości x pochodzą od „eksperta” lub „wyjątku”
noɥʇʎԀʎzɐɹƆ
Co ważniejsze, ich wszechstronna lista przykładów.
Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功
19

Jak te nowe kategorie odnoszą się do istniejących kategorii wartości i wartości?

Wartość C ++ 03 jest nadal wartością C ++ 11, podczas gdy wartość C ++ 03 w C ++ 11 nazywa się wartością.

fredoverflow
źródło
14

Jeden dodatek do doskonałych odpowiedzi powyżej, w kwestii, która mnie zdezorientowała, nawet po przeczytaniu Stroustrupa i przekonaniu, że rozumiem rozróżnienie wartość / wartość. Kiedy widzisz

int&& a = 3,

to bardzo kuszące, aby przeczytać ten int&&typ i stwierdzić, że ajest to wartość. To nie jest:

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

ama nazwę i jest ipso facto wartością. Nie myśl o tym &&jako o części tego typu a; to po prostu coś, co mówi ci, z czym awolno się wiązać.

Ma to szczególne znaczenie dla T&&argumentów typu w konstruktorach. Jeśli napiszesz

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

skopiujesz _tdo t. Potrzebujesz

Foo::Foo(T&& _t) : t{std::move(_t)} {}jeśli chcesz się przenieść. Czy mój kompilator ostrzegłby mnie przed pominięciem move!

Mohan
źródło
1
Myślę, że ta odpowiedź może zostać wyjaśniona. „Z czym amożna się wiązać”: Jasne, ale w liniach 2 i 3 twoje zmienne to c & b, i to nie jest to, co wiąże się z tym, a typ ajest tutaj nieistotny, prawda? Linie byłyby takie same, gdyby azostały zadeklarowane int a. Główna różnica polega tutaj na tym, że w wierszu 1 nie trzeba constprzypisywać do 3.
Felix Dombek,
12

Ponieważ poprzednie odpowiedzi wyczerpująco obejmowały teorię dotyczącą kategorii wartości, jest jeszcze jedna rzecz, którą chciałbym dodać: możesz się nią bawić i przetestować.

W przypadku niektórych praktycznych eksperymentów z kategoriami wartości można skorzystać ze specyfikatora decltype . Jego zachowanie wyraźnie rozróżnia trzy podstawowe kategorie wartości (xvalue, lvalue i prvalue).

Korzystanie z preprocesora pozwala zaoszczędzić trochę pisania ...

Główne kategorie:

#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

Mieszane kategorie:

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

Teraz możemy odtworzyć (prawie) wszystkie przykłady z preferencji cp na kategorię wartości .

Oto kilka przykładów z C ++ 17 (dla 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)); 

Kategorie mieszane są trochę nudne, kiedy już wymyślisz kategorię podstawową.

Aby uzyskać więcej przykładów (i eksperymentów), sprawdź poniższy link w eksploratorze kompilatorów . Jednak nie zawracaj sobie głowy czytaniem zestawu. Dodałem wiele kompilatorów, aby upewnić się, że działa we wszystkich popularnych kompilatorach.

thebrandre
źródło
Myślę, że w przeciwnym razie #define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)powinieneś #define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))spojrzeć na to, co się stanie, jeśli wy &&dwoje IS_GLVALUE.
Gabriel Devillers