konwersja lvalue do rvalue implicit

Widzę termin "konwersja lvalue-to-rvalue" używany w wielu miejscach w całym standardzie C++. Tego rodzaju konwersja jest często dokonywana w sposób dorozumiany, o ile mogę powiedzieć.

Nieoczekiwaną (dla mnie) cechą frazowania ze standardu jest to, że decydują się traktować lvalue-to-rvalue jako konwersję. Co by było, gdyby powiedzieli, że wartość GL jest zawsze akceptowalna zamiast wartości PR. Czy to zdanie rzeczywiście ma inne znaczenie? Na przykład czytamy, że lvalues i xvalues są przykłady wartości GL. Nie czytamy, że wartości lvalu i xvalue są zamieniane na wartości GL. Czy jest jakaś różnica w znaczeniu?

Przed pierwszym zetknięciem się z tą terminologią, modelowałem wartości LV i R mentalnie mniej więcej w następujący sposób: "wartości LV sązawsze zdolne do działania jako wartości R, ale dodatkowo mogą pojawić się po lewej stronie = i po prawej stronie &".

Jest to dla mnie intuicyjne zachowanie, że jeśli mam nazwę zmiennej, to mogę umieścić to nazwisko wszędzie tam, gdzie umieściłbym je dosłownie. Model ten wydaje się zgodny z terminologią konwersji lvalue-to-rvalue implicit conversions używaną w standardzie, o ile taka konwersja implicit jest gwarantowana.

Ale ponieważ używają tej terminologii, zacząłem się zastanawiać, czy ukryta konwersja lvalue-to-rvalue może się nie zdarzyć w niektórych przypadkach. To znaczy, może mój model umysłowy jest tutaj zły. Oto istotna część standardu: (dzięki komentującym).

Gdy wartość GL pojawia się w kontekście, w którym oczekuje się wartości PR, wartość GL jest konwertowana na wartość PR; patrz 4.1, 4.2 i 4.3. [Uwaga: próba powiązania odniesienia rvalue z lvalue nie jest takim kontekstem; patrz 8.5.3 .- Uwaga końcowa]

Rozumiem, że to, co opisują w notce jest następujące:

int x = 1;
int && y = x; //in this declaration context, x won't bind to y.
// but the literal 1 would have bound, so this is one context where the implicit 
// lvalue to rvalue conversion did not happen.  
// The expression on right is an lvalue. if it had been a prvalue, it would have bound.
// Therefore, the lvalue to prvalue conversion did not happen (which is good). 

Więc moje pytanie brzmi (są):

1) Czy ktoś mógłby wyjaśnić konteksty, w których ta konwersja może mieć miejsce bezwarunkowo? W szczególności, inne niż kontekst powiązania z odniesieniem rvalue, czy istnieją inne przypadki, w których konwersje lvalue-to-rvalue nie zdarzają się pośrednio?

2) również nawias [Note:...] w klauzuli sprawia, że wydaje się, że mogliśmy to rozgryźć z poprzedniego zdania. Która to część standardu?

3) Czy to oznacza, że powiązanie rvalue-reference nie jest kontekstem, w którym oczekujemy wyrażenia prvalue (po prawej)?

4) podobnie jak inne konwersje, czy konwersja glvalue-to-prvalue dotyczy pracy w trybie runtime, która pozwoli mi ją obserwować?

Moim celem tutaj nie jest pytanie, czy jest pożądane, aby pozwolić na takie nawrócenie. Staram się nauczyć wyjaśniać sobie zachowanie tego kodu używając standardu jako punktu wyjścia.

Dobra odpowiedź mogłaby przejść przez cytat, który umieściłem powyżej i wyjaśnić (na podstawie analizy tekstu), czy notatka w nim jest również Ukryta z jego tekstu. To może dodać jakieś inne cytaty, które dają mi znać inne konteksty, w których ta konwersja może nie mieć miejsca w sposób dorozumiany lub wyjaśniać, że nie ma już takich kontekstów. Być może Ogólna dyskusja o tym, dlaczego glvalue do prvalue jest uważane za konwersję.

Author: Dan Nissenbaum, 2013-12-31

2 answers

Myślę, że konwersja lvalue-to-rvalue jest czymś więcej niż tylko użyj lvalue gdzie rvalue jest wymagane . Może tworzyć kopię klasy i zawsze daje wartość, a nie obiekt.

Używam n3485 dla "C++11" i n1256 dla "C99".


Obiekty i wartości

Najbardziej zwięzły opis jest w C99 / 3.14:

Obiekt

Region przechowywania danych w środowisku wykonawczym, zawartość które mogą reprezentować wartości

Jest też trochę w C++11 / [intro.obiekt] / 1

Niektóre obiekty są polimorficzne ; implementacja generuje informacje związane z każdy taki obiekt, który umożliwia określenie typu tego obiektu podczas wykonywania programu. W przypadku innych obiektów interpretacja znalezionych w nich wartości zależy od typu wyrażeń używanych do uzyskania do nich dostępu.

Więc obiekt zawiera wartość (może zawierać).


Kategorie wartości

Pomimo nazwy, kategorie wartości klasyfikują wyrażenia, a nie wartości. lvalue-wyrażenia nawet nie mogą być traktowane jako wartości.

Pełna Taksonomia / Kategoryzacja znajduje się w [basic.lval]; oto dyskusja StackOverflow .

Oto Części o obiektach:

  • An lvalue ([...]) oznacza funkcję lub obiekt. [...]
  • An xvalue (wartość "wygasająca") odnosi się również do obiektu [...]
  • a glvalue ("uogólniony" lvalue) jest lvalue lub xvalue.
  • An rvalue ([...]) jest xvalue, tymczasowym obiektem lub jego podobiektem, lub wartością, która nie jest powiązana z obiektem.
  • prvalue ("czysta" rvalue) jest wartością R, która nie jest xvalue. [...]

Zwróć uwagę na wyrażenie "wartość, która nie jest powiązana z obiektem". Również zauważ, że ponieważ wyrażenia xvalue odnoszą się do obiektów, wartości true muszą zawsze występować jako prvalue-expressions.


Konwersja lvalue-to-rvalue

Jak wskazuje przypis 53, powinien on być teraz nazywany "konwersją wartości glvalue-to-prvalue". Po pierwsze, oto cytat:

1 wartość GL typu non-function, non-array T może być przekonwertowana na wartość prvalue. Jeśli T jest typem niekompletnym, program, który wymaga tej konwersji, jest źle uformowany. Jeśli obiekt, do którego odnosi się glvalue, nie jest obiektem typu T i nie jest obiektem typu pochodnego z T, lub jeśli obiekt jest niezainicjalizowany, program to powoduje, że ta konwersja Ma nieokreślone zachowanie. Jeśli T jest typem nieklasowym, typem wartości PR jest CV-niekwalifikowana wersja T. W przeciwnym razie typem wartości PR jest T.

Ten pierwszy akapit określa wymagania i wynikowy typ konwersji. Nie jest jeszcze zaniepokojony Efekty konwersji (inne niż nieokreślone zachowanie).

2 gdy konwersja lvalue-to-rvalue wystąpi w nieocenionym operandie lub jego podwyrażeniu, wartość zawarta w obiekcie odniesienia nie jest dostępna. W przeciwnym razie, jeśli wartość glvalue ma typ klasy, konwersja copy-inicjalizuje wartość tymczasową typu T z wartości glvalue i wynikiem konwersji jest wartość prvalue dla wartości tymczasowej. W przeciwnym razie, jeśli wartość glvalue ma (ewentualnie CV-qualified) type std::nullptr_t, the wynik prvalue jest stałą wskaźnika null. W przeciwnym razie wartością zawartą w obiekcie wskazanym przez glvalue jest wynik prvalue.

Argumentowałbym, że zobaczysz konwersję lvalue-to-rvalue najczęściej stosowaną do typów spoza klasy. Na przykład,

struct my_class { int m; };

my_class x{42};
my_class y{0};

x = y;

Wyrażenie x = yNie stosuje konwersję lvalue-to-rvalue do y (nawiasem mówiąc, stworzyłoby tymczasowe my_class). Powodem jest to, że x = y interpretuje się jako x.operator=(y), który pobiera y za domyślne przez odniesienie , a nie przez wartość (DLA powiązania odniesienia, patrz poniżej; nie może powiązać wartości r, ponieważ byłby to obiekt tymczasowy inny niż y). Jednak domyślna definicja my_class::operator= stosuje konwersję lvalue-to-rvalue do x.m.

Dlatego najważniejszą częścią wydaje mi się być

W Przeciwnym Razie wartością zawartą w obiekcie wskazanym przez glvalue jest prvalue wynik.

Tak więc zazwyczaj konwersja lvalue-to-rvalue będzie po prostu odczytywać wartość z obiektu . Nie jest to tylko konwersja bez operacji pomiędzy kategoriami wartości (wyrażenia); może nawet utworzyć tymczasowy przez wywołanie konstruktora kopiującego. A konwersja lvalue - to-rvalue zawsze zwraca wartość prvalue , a nie (tymczasowy) obiekt.

Zauważ, że konwersja lvalue-to-rvalue nie jest jedyną konwersją, która konwertuje lvalue na wartość PR: Istnieje również konwersja tablicy na wskaźnik i konwersja funkcji na wskaźnik.


Wartości i wyrażenia

Większość wyrażeń nie daje obiektów [[potrzebne cytowanie]] . Jednak ID-wyrażenie może być identyfikatorem, który oznacza encję. Obiekt jest bytem, więc istnieją wyrażenia, które dają obiekty:

int x;
x = 5;

Lewa strona przypisanie-wyrażenie x = 5 również musi być ekspresja. x tutaj jest ID-wyrażenie , ponieważ {[23] } jest identyfikatorem. Wynikiem tego ID-wyrażenia jest obiekt oznaczony przez x.

Wyrażenia stosują implicit conversions: [expr] / 9

Gdy wyrażenie glvalue pojawia się jako operand operatora, który oczekuje wartości PR dla tego operandu, do konwersji wyrażenia na wartość lvalue-to-rvalue, array-to-pointer lub function-to-pointer są stosowane standardowe konwersje prvalue.

I /10 o zwykłych konwersjach arytmetycznych oraz /3 o konwersjach zdefiniowanych przez użytkownika.

Chciałbym teraz zacytować operator, który "oczekuje wartości prvalue dla tego operanda", ale nie może znaleźć żadnych, ale rzuca. Na przykład [expr.dynamiczny.cast] / 2 "Jeśli T jest typem wskaźnika, v [operand] jest wartością wstępną wskaźnika do zakończenia typu klasy".

Zwykłe konwersje arytmetyczne wymagane przez wiele operatorów arytmetycznych wywołują konwersja lvalue-to-rvalue pośrednio poprzez standardową konwersję. Wszystkie standardowe konwersje z wyjątkiem trzech, które konwertują z wartości lvalu na wartości R oczekują wartości PR.

Proste przypisanie nie wywołuje jednak zwykłych konwersji arytmetycznych. Jest on zdefiniowany w [expr.ass] / 2 as:

W prostym przypisaniu (=) wartość wyrażenia zastępuje wartość obiektu, do którego odnosi się lewy operand.

Więc chociaż nie wymaga wyraźnie wyrażenie prvalue po prawej stronie wymaga wartości . Nie jest dla mnie jasne, czy ta ściśle wymaga konwersji lvalue-to-rvalue. Istnieje argument, że dostęp do wartości niezainicjalizowanej zmiennej powinien zawsze wywoływać nieokreślone zachowanie( Zobacz także CWG 616), bez względu na to, czy jest to przypisanie jej wartości do obiektu, czy dodanie jej wartości do innej wartości. Ale to nieokreślone zachowanie jest wymagane tylko dla konwersji lvalue-to-rvalue (AFAIK), który wtedy powinien być jedynym sposobem dostępu do wartości przechowywanej w obiekcie.

Jeśli ten bardziej pojęciowy widok jest poprawny, że potrzebujemy konwersji lvalue-to-rvalue, aby uzyskać dostęp do wartości wewnątrz obiektu, wtedy byłoby znacznie łatwiej zrozumieć, gdzie jest (i musi być) zastosowana.


Inicjalizacja

Podobnie jak w przypadku prostego przypisania, istnieje dyskusja czy konwersja lvalue-to-rvalue jest wymagana do zainicjowania innej obiekt:

int x = 42; // initializer is a non-string literal -> prvalue
int y = x;  // initializer is an object / lvalue

Dla typów podstawowych, [dcl.init] / 17 ostatni punkt mówi:

W przeciwnym razie, wartość początkowa inicjalizowanego obiektu jest (ewentualnie przekonwertowana) wartością wyrażenia inicjalizującego. Standardowe konwersje będą używane, jeśli to konieczne, do konwersji wyrażenia inicjalizatora na CV-niekwalifikowana wersja typu docelowego; nie są brane pod uwagę konwersje zdefiniowane przez użytkownika. Jeśli nie można wykonać konwersji, inicjalizacja jest źle ukształtowany.

Wspomniano jednak również o wartości wyrażenia inicjalizującego. Podobnie jak w prostym-przypisaniu-wyrażeniu, możemy przyjąć to jako pośrednie wywołanie konwersji lvalue-to-rvalue.


Oprawa referencyjna

Jeśli widzimy konwersję lvalue-to-rvalue jako sposób dostępu do wartości obiektu (plus utworzenie tymczasowego dla operandów typu klasy), rozumiemy, że jest to , a nie stosowane ogólnie do wiązania do odniesienia: odniesienie jest lvalue, zawsze odnosi się do obiektu. Więc jeśli połączymy wartości z referencjami, będziemy musieli utworzyć tymczasowe obiekty zawierające te wartości. I tak jest rzeczywiście w przypadku, gdy wyrażenie inicjalizujące odniesienia jest wartością prvalue (która jest wartością lub obiektem tymczasowym):

int const& lr = 42; // create a temporary object, bind it to `r`
int&& rv = 42;      // same

Wiązanie prvalue z referencją lvalue jest zabronione, ale prvalues typów klas z funkcjami konwersji, które dają odniesienia lvalue mogą być związane z referencjami lvalue przerobiony Typ.

Pełny opis wiązania odniesienia w [dcl.init.ref] jest dość długi i raczej off-topic. Myślę, że istotą tego pytania jest to, że odniesienia odnoszą się do obiektów, dlatego nie ma konwersji glvalue-to-prvalue (object-to-value).

 23
Author: dyp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:46

On glvalues: glvalue ("uogólniony" lvalue) jest wyrażeniem, które jest albo lvalue, albo xvalue. Wartość glvalue może być domyślnie przekonwertowana na wartość prvalue za pomocą konwersji lvalue-to-rvalue, array-to-pointer lub function-to-pointer.

Transformacje Lvalue są stosowane, gdy argument lvalue (np. odniesienie do obiektu) jest używany w kontekście, w którym oczekiwana jest wartość rvalue (np. liczba).

Konwersja Lvalue na rvalue
A glvalue of any non-function, non-array Typ T może być niejawnie zamieniony na prvalue tego samego typu . Jeśli T jest typem nieklasowym, ta konwersja usuwa również kwalifikatory cv. O ile nie zostanie napotkana w niewycenionym kontekście (w operandie sizeof, typeid, noexcept lub decltype), ta konwersja skutecznie kopiuje-konstruuje tymczasowy obiekt typu T używając oryginalnej wartości glvalue jako argumentu konstruktora, a ten tymczasowy obiekt jest zwracany jako wartość prvalue. Jeśli wartość glvalue ma typ std:: nullptr_t, wynikowa wartość prvalue jest stała wskaźnika null nullptr.

 2
Author: callisto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-08 07:44:32