前書き
ISOC++11 (正式には ISO/IEC 14882:2011) は、C++ プログラミング言語の標準の最新バージョンです。これには、いくつかの新しい機能と概念が含まれています。たとえば、次のとおりです。
- 右辺値参照
- xvalue、glvalue、prvalue 式の値のカテゴリ
- セマンティクスを移動する
新しい式の値カテゴリの概念を理解したい場合は、右辺値と左辺値の参照があることに注意する必要があります。右辺値を非 const 右辺値参照に渡すことができることを知っておくことをお勧めします。
int& r_i=7; // compile error
int&& rr_i=7; // OK
作業草案 N3337 (発行された ISOC++11 標準に最も類似した草案) から Lvalues と rvalues というタイトルのサブセクションを引用すると、値カテゴリの概念のいくつかの直感を得ることができます。
3.10 左辺値と右辺値 [basic.lval]
1 式は、図 1 の分類に従って分類されます。
- 左辺値 (左辺値は代入式の左側に現れる可能性があるため、歴史的にそう呼ばれています) は、関数またはオブジェクトを指定します。[ 例: E がポインター型の式の場合、*E は、E が指すオブジェクトまたは関数を参照する左辺値式です。別の例として、戻り値の型が左辺値参照である関数を呼び出した結果は、左辺値です。—終わりの例]
- xvalue (「eXpiring」値) もオブジェクトを参照します。通常は、オブジェクトの有効期間の終わりが近づいています (たとえば、そのリソースが移動される可能性があるため)。xvalue は、右辺値参照 (8.3.2) を含む特定の種類の式の結果です。[ 例: 戻り値の型が右辺値参照である関数を呼び出した結果は、xvalue です。—終わりの例]
- glvalue (「一般化された」左辺値) は、左辺値または xvalue です。
- 右辺値 (右辺値は代入式の右側に現れる可能性があるため、歴史的にそう呼ばれています) は、xvalue、一時オブジェクト (12.2) またはそのサブオブジェクト、またはオブジェクトに
関連付けられて
いない値です。
- prvalue (「純粋な」右辺値) は、xvalue ではない右辺値です。【例:戻り値の型が参照でない関数の呼び出し結果は
prvalue。12、7.3e5、または
true などのリテラルの値も prvalue です。—終わりの例]
すべての式は、この分類法の基本分類の 1 つ (左辺値、x 値、または前値) に正確に属します。式のこのプロパティは、その値カテゴリと呼ばれます。
しかし、このサブセクションが概念を明確に理解するのに十分であるかどうかはよくわかりません。「通常」は実際には一般的ではなく、「寿命の終わり近く」は実際には具体的ではなく、「右辺値参照を含む」は実際には明確ではないためです。 「例: 戻り値の型が右辺値参照である関数を呼び出した結果は xvalue です。」ヘビが尻尾を噛んでいるような音。
プライマリ バリュー カテゴリ
すべての式は、厳密に 1 つのプライマリ値カテゴリに属します。これらの値カテゴリは、lvalue、xvalue、および prvalue カテゴリです。
左辺値
式 E は、E の外部からアクセスできるようにする ID (アドレス、名前、またはエイリアス) を既に持っているエンティティを 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;
}
x値
式 E は、次の場合にのみ xvalue カテゴリに属します。
— 暗黙的または明示的に、戻り値の型が返されるオブジェクトの型への右辺値参照である関数を呼び出した結果、または
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;
}
— オブジェクト型への右辺値参照へのキャスト、または
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;
}
— オブジェクト式が xvalue である非参照型の非静的データ メンバーを指定するクラス メンバー アクセス式、または
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;
}
— 最初のオペランドが xvalue で、2 番目のオペランドがデータ メンバーへのポインターであるメンバーへのポインター式。
上記のルールの効果は、オブジェクトへの名前付き右辺値参照が左辺値として扱われ、オブジェクトへの名前なし右辺値参照が xvalue として扱われることに注意してください。関数への右辺値参照は、名前が付けられているかどうかにかかわらず、左辺値として扱われます。
#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;
}
値
式 E が prvalue カテゴリに属するのは、E が lvalue カテゴリにも 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;
}
混合価値カテゴリー
さらに 2 つの重要な混合値カテゴリがあります。これらの値カテゴリは、rvalue カテゴリと glvalue カテゴリです。
右辺値
式 E が右辺値カテゴリに属するのは、E が xvalue カテゴリまたは prvalue カテゴリに属している場合のみです。
この定義は、式 E が右辺値カテゴリに属することを意味することに注意してください。これは、E が、E YET の外部でアクセス可能にするアイデンティティを持たないエンティティを参照する場合に限られます。
glvalues
E が lvalue カテゴリまたは xvalue カテゴリに属する場合に限り、式 E は glvalue カテゴリに属します。
実用的な規則
Scott Meyer は、右辺値と左辺値を区別するための非常に役立つ経験則を公開しています。
- 式のアドレスを取得できる場合、その式は左辺値です。
- 式の型が左辺値参照 (T& や const T& など) の場合、その式は左辺値です。
- それ以外の場合、式は右辺値です。概念的に (通常は実際にも)、右辺値は、関数から返されたオブジェクトや暗黙的な型変換によって作成されたオブジェクトなどの一時オブジェクトに対応します。ほとんどのリテラル値 (10 や 5.3 など) も右辺値です。