web-dev-qa-db-ja.com

左辺値から右辺値への暗黙の変換

C++標準全体の多くの場所で「左辺値から右辺値への変換」という用語が使用されています。この種の変換は、私が知る限り、暗黙的に行われることがよくあります。

標準からの言い回しの予期しない(私にとっての)特徴の1つは、左辺値から右辺値への変換を扱うことを決定することです。彼らが、prvalueではなくglvalueが常に受け入れられると言っていたらどうでしょう。そのフレーズは実際に別の意味を持つのでしょうか?たとえば、lvaluesとxvaluesがglvaluesの例であることを読みます。 lvaluesとxvaluesがglvaluesに変換可能であることは読みません。意味に違いはありますか?

この用語に初めて遭遇する前に、私は多かれ少なかれ以下のように左辺値と右辺値を精神的にモデル化していました:「左辺値は常に右辺値として機能できますが、さらに=、および& "の右側。

これは、私が変数名を持っている場合、リテラルを配置する場所ならどこにでもその名前を配置できるという直感的な動作です。このモデルは、この暗黙的な変換が行われることが保証されている限り、標準で使用されている左辺値から右辺値への暗黙的な変換の用語と一致しています。

しかし、彼らはこの用語を使用しているので、暗黙的な左辺値から右辺値への変換が場合によっては失敗しないのではないかと考え始めました。つまり、おそらく私のメンタルモデルはここでは間違っています。標準の関連部分は次のとおりです:(コメント者に感謝)。

Prvalueが期待されるコンテキストにglvalueが現れると、glvalueはprvalueに変換されます。 4.1、4.2、および4.3を参照してください。 [注:右辺値参照を左辺値にバインドする試みは、そのようなコンテキストではありません。 8.5.3を参照してください。—エンドノート]

彼らがメモで説明している内容は次のとおりです:

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). 

だから、私の質問は(です):

1)この変換が暗黙的に発生する可能性のあるコンテキストを誰かが明確にできますか?具体的には、右辺値参照へのバインドのコンテキスト以外に、左辺値から右辺値への変換が暗黙的に行われない他の場所はありますか?

2)また、句の括弧[Note:...]は、以前の文からそれを理解できたように見えるようにします。それは規格のどの部分ですか?

3)それは、右辺値参照バインディングが、右辺のprvalue式を期待するコンテキストではないことを意味しますか?

4)他の変換と同様に、glvalueからprvalueへの変換には、実行時にそれを観察できるようにする作業が含まれますか?

ここでの私の目的は、そのような変換を許可することが望ましいかどうかを尋ねることではありません。標準を出発点として、このコードの動作を自分で説明する方法を学びます。

良い答えは、私が上に置いた引用を通り抜けて、(テキストの解析に基づいて)その中のメモもそのテキストから暗黙的であるかどうかを説明するでしょう。次に、この変換が暗黙的に実行されない可能性のある他のコンテキストを知らせる他の引用符を追加するか、そのようなコンテキストがないことを説明します。おそらく、glvalueからprvalueが変換と見なされる理由についての一般的な議論。

41
orm

左辺値から右辺値への変換は、右辺値が必要な左辺値を使用するだけではないと思います。クラスのコピーを作成でき、常にオブジェクトではなくvalueを生成します。

「C++ 11」にはn3485、「C99」にはn1256を使用しています。


オブジェクトと値

最も簡潔な説明はC99/3.14です。

オブジェクト

実行環境のデータストレージの領域。その内容は値を表すことができます

C++ 11/[intro.object]/1にも少しあります

一部のオブジェクトはpolymorphicです。実装は、そのような各オブジェクトに関連付けられた情報を生成し、プログラムの実行中にそのオブジェクトのタイプを判別できるようにします。他のオブジェクトの場合、そこにある値の解釈は、それらにアクセスするために使用される式のタイプによって決まります。

したがって、オブジェクトには値が含まれます(含むことができます)。


値のカテゴリ

その名前にもかかわらず、value categoriesは値ではなく式を分類します。 lvalue-expressionsはcannotでも値と見なされます。

完全な分類/分類は[basic.lval]にあります。 ここにStackOverflowの議論があります

オブジェクトに関する部分は次のとおりです。

  • lvalue([...])は、関数またはオブジェクトを指定します。 [...]
  • xvalue(「eXpiring」値)もオブジェクトを参照します[...]
  • glvalue(「一般化された」左辺値)は左辺値またはx値です。
  • rvalue([...])は、xvalue、一時オブジェクトまたはそのサブオブジェクト、またはオブジェクトに関連付けられていない値です。
  • Prvalue(「純粋な」右辺値)は、x値ではない右辺値です。 [...]

「オブジェクトに関連付けられていない値」というフレーズに注意してください。また、xvalue-expressionsはオブジェクトを参照するため、truevaluesは常にprvalue-expressionsとして発生する必要があることに注意してください。


左辺値から右辺値への変換

脚注53に示されているように、これは「glvalueからprvalueへの変換」と呼ばれるはずです。まず、ここに引用があります:

1非関数、非配列型Tのglvalueは、prvalueに変換できます。 Tが不完全な型である場合、この変換を必要とするプログラムの形式は正しくありません。 glvalueが参照するオブジェクトがT型のオブジェクトではなく、Tから派生した型のオブジェクトでもない場合、またはオブジェクトが初期化されていない場合、この変換を必要とするプログラムの動作は未定義です。 Tが非クラス型の場合、prvalueの型は、Tのcv非修飾バージョンです。それ以外の場合、prvalueのタイプはTです。

この最初の段落では、要件と変換の結果のタイプを指定します。 (未定義の動作以外の)変換のeffectsにはまだ関係していません。

2未評価のオペランドまたはその部分式で左辺値から右辺値への変換が発生すると、参照されるオブジェクトに含まれる値にはアクセスされません。それ以外の場合、glvalueにクラスタイプがある場合、変換はglvalueからTタイプの一時ファイルをコピー初期化し、変換の結果は一時ファイルのprvalueになります。それ以外の場合、glvalueが(おそらくcv修飾された)タイプ_std::nullptr_t_を持っていると、prvalueの結果はnullポインター定数になります。それ以外の場合、glvalueで示されるオブジェクトに含まれる値はprvalueの結果です。

左辺値から右辺値への変換は、クラス以外の型に最も頻繁に適用されると思います。例えば、

_struct my_class { int m; };

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

x = y;
_

式_x = y_は、notを使用して、左辺値から右辺値への変換をyに適用します(これにより、一時的な_my_class_が作成されます)。その理由は、_x = y_がx.operator=(y)として解釈され、デフォルトでyby value(ではなく、by referenceと解釈されるためです。参照バインディングについては、以下を参照してください。これは、yとは異なる一時オブジェクトになるため、右辺値をバインドできません。ただし、_my_class::operator=_のデフォルト定義では、_x.m_への左辺値から右辺値への変換が適用されます。

したがって、私にとって最も重要な部分は

それ以外の場合、glvalueで示されるオブジェクトに含まれる値はprvalueの結果です。

したがって、通常、左辺値から右辺値への変換では、オブジェクトから値を読み取ります。これは、値(式)のカテゴリー間の単なる変換ではありません。コピーコンストラクタを呼び出すことで一時的なものを作成することもできます。また、左辺値から右辺値への変換では、常に(一時的な)objectではなく、prvaluevalueが返されます。

Lvalueからrvalueへの変換は、lvalueをprvalueに変換する唯一の変換ではないことに注意してください。配列からポインターへの変換と関数からポインターへの変換もあります。


値と式

ほとんどの式はオブジェクトを生成しません[[引用が必要]]。ただし、id-expressionは、entityを示すidentifierにすることができます。オブジェクトはエンティティなので、オブジェクトを生成する式があります。

_int x;
x = 5;
_

assignment-expression_x = 5_の左側も式である必要があります。 xは識別子であるため、xid-expressionです。このid-expressionの結果はxで示されるオブジェクトです。

式は暗黙的な変換を適用します:[expr]/9

Glvalue式が、そのオペランドのprvalueを期待する演算子のオペランドとして現れる場合は常に、lvalueからrvalue、配列からポインター、または関数からポインターへの標準変換が適用され、式がprvalueに変換されます。

/ 10は通常の算術変換で、/ 3はユーザー定義の変換です。

「そのオペランドのprvalueを期待する」が、キャスト以外は見つからない演算子を引用するのが好きです。たとえば、[expr.dynamic.cast]/2 "Tがポインター型の場合、v [オペランド]は完全なクラス型へのポインターのprvalueでなければなりません。".

多くの算術演算子に必要な通常の算術変換は、使用される標準変換を介して間接的に左辺値から右辺値への変換を呼び出します。左辺値から右辺値に変換する3つを除くすべての標準変換は、prvalueを期待します。

ただし、単純な割り当てでは、通常の算術変換は呼び出されません。 [expr.ass]/2で次のように定義されています。

単純な代入(_=_)では、式の値は、左のオペランドによって参照されるオブジェクトの値を置き換えます。

したがって、右側に明示的なprvalue式は必要ありませんが、valueが必要です。このstrictlyが左辺値から右辺値への変換を必要とするかどうかは、私にはわかりません。初期化されていない変数の値にアクセスすると、未定義の動作を呼び出す必要があるという引数があります(値をオブジェクトに割り当てるか、その値を別の値に追加するかに関わらず、 CWG 616 も参照)。ただし、この未定義の動作は、左辺値から右辺値への変換(AFAIK)にのみ必要であり、オブジェクトに格納されている値にアクセスする唯一の方法である必要があります。

このより概念的なビューが有効であり、オブジェクト内の値にアクセスするために左辺値から右辺値への変換が必要な場合、それが適用される場所(および適用する必要がある場所)を理解するのははるかに簡単です。


初期化

単純な代入と同様に、別のオブジェクトを初期化するために左辺値から右辺値への変換が必要かどうかに関係なく、 ディスカッション があります。

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

基本的なタイプの場合、[dcl.init]/17の最後の箇条書きは次のように述べています。

それ以外の場合、初期化されるオブジェクトの初期値は、初期化子式の(変換された可能性がある)値です。必要に応じて、標準変換を使用して、イニシャライザ式を宛先タイプのcv非修飾バージョンに変換します。ユーザー定義の変換は考慮されません。変換を実行できない場合、初期化の形式が正しくありません。

ただし、イニシャライザ式のvalueについても触れています。 simple-assignment-expressionと同様に、これを左辺値から右辺値への変換の間接的な呼び出しと見なすことができます。


参照バインディング

オブジェクトの値にアクセスする方法として、左辺値から右辺値への変換(およびクラス型のオペランドの一時的な作成)が見られる場合、それはnotがバインディングに一般的に適用されることを理解しています参照への参照:参照は左辺値であり、常にオブジェクトを参照します。したがって、値を参照にバインドする場合、それらの値を保持する一時オブジェクトを作成する必要があります。そして、これは確かに、参照の初期化子式がprvalue(値または一時オブジェクト)である場合です。

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

Prvalueをlvalue参照にバインドすることは禁止されていますが、lvalue参照を生成する変換関数を持つクラス型のprvalueは、変換された型のlvalue参照にバインドできます。

[dcl.init.ref]での参照バインディングの完全な説明は、かなり長く、かなり話題外です。この質問に関連する本質は、参照がオブジェクトを参照することであり、したがってglvalueからprvalue(オブジェクトから値)への変換は行われないということです。

31
dyp

Glvaluesの場合:glvalue(「一般化された」lvalue)は、lvalueまたはxvalueのいずれかの式です。 glvalueは、lvalueからrvalue、配列からポインター、または関数からポインターへの暗黙的な変換により、暗黙的にprvalueに変換できます。

左辺値変換が適用されるのは、右辺値(数値など)が期待されるコンテキストで左辺値引数(オブジェクトへの参照など)が使用されている場合です。

左辺値から右辺値への変換
関数でも配列でもないタイプTのglvalueは、暗黙的にprvalue 同じタイプのに変換できます。 Tが非クラス型の場合、この変換によりcv-qualifiersも削除されます。未評価のコンテキスト(sizeof、typeid、noexcept、またはdecltypeのオペランドで)に遭遇しない限り、この変換は、コンストラクター引数として元のglvalueを使用してタイプTの一時オブジェクトを効果的にコピー構築し、その一時オブジェクトはprvalueとして返されます。 glvalueの型がstd :: nullptr_tの場合、結果のprvalueはnullポインター定数nullptrです。

2
callisto