きままにブログ

プログラミングを主とした私のメモ帳です。寂しいのでコメントください笑

左辺値と右辺値

右辺値と左辺値

C++では式の型(T, const T, volatile T, T&, T&&)以外にもうひとつ式の値を分類する用語がある。いままで左辺値、右辺値といっていたような類いである。

int x;
x = 3;

いままではxが=の左側にあるからxは左辺値、3は右側にあるから右辺値といったような分類をされていたが、いまのC++では少し拡張された分類となっている。ここでいうxはlvalueであり、3はprvalueというものに相当する。

これらの分類は大きく分けて2種類あり、lvalueとrvalueに分類される。〔もちろんlとrの由来は左と右である。〕

変更可能な左辺値

蛇足ながら、コンパイルエラー等で見かける「変更可能な左辺値」とは、「変更不可能でない左辺値」のことである。変更不可能な左辺値は以下の通り。

  • 配列型
  • const修飾子がつけられた型
  • メンバの1つがconst修飾されている型
  • 不完全な型

変更可能な左辺値は、代入演算子=の左側に記述できる。

代入演算子の結果

代入演算子=の結果を利用できることはC言語でもよくある記述 x = y = 3; などからわかる。ところでこの結果は右辺値だろうか? 左辺値だろうか? C言語では右辺値である。ところがC++では標準で左辺値扱いされる。即ち以下の記述は妥当である。

int x;
(x = 1) = 2; // ok

もちろん演算子オーバーロードがなされた場合、それに依存する、

lvalue

lvalueとは、変数名がつけられたオブジェクトやポインタが指し示すオブジェクトである。

変数

int x;
int* p = &x;

スタック上にあるint型オブジェクトxはlvalueである。また、xのポインタであるpも、変数名が付けられたオブジェクトであり、lvalueである。更にpの指し示すオブジェクトも(要するにxだが)lvalueである。

参照

int x; // lvalue
int& lr = x; // lvalue
int&& rr = 0; // lvalue

これらx, lr, rrはいずれも変数名が付けられたオブジェクトだからlvlalueである。

lvalueのメンバ変数

struct S {
	int x;
};

クラスSのメンバ変数xはlvalueである。同様に左辺値参照、右辺値参照〔ここでの左辺値・右辺値は本トピックとは無関係である〕であってもlvalueである。

メンバーへのポインタの実態

struct S { };
int S::* x;
S s;
s.*x; // lvalue
static_cast<S&&>(s).*x; // lvalue

lvalueでもrvalueでもメンバへのポインタを経由してアクセスしたものはlvalueである。

左辺値参照を返す関数

int& func(int& x) {
	return x;
}

int x;
func(x) = 3;

このように左辺値参照を返す関数はlvalueである。

文字列リテラル

"abc"等は左辺値(配列)であるが、"abc"[0]に代入しようとしてもできない。というのもconstだからだ。

xvalue

xvalueは、いつ消滅してもおかしくないオブジェクトである。由来はeXpiring valueから。従ってxvalueなオブジェクトは今後使われることを想定しなくても良い。これはmove semanticsに利用される。

右辺値参照を返す関数

int&& func() {
	return 1;
}

func(); // xvalue

右辺値参照を返す関数はxvalueである。std::move()は右辺値参照を返すのでxvalueである。

右辺値参照へのキャスト

int x;
static_cast<int&&>(x); // xvalue

std::move()のしていることは右辺値参照へのキャストである。

xvalueの非参照メンバ変数

参照型のメンバ変数は上述の通りlvalueである。一方でxvalueの非参照メンバ変数はxvalueである。

struct S {
	int x;
};

S s;
static_cast<S&&>(s).x; // xvalue

prvalue

簡単に言うとlvalueやxvalueに分類されないその他である。

リテラル

1とか0x01とか'a'とかが該当する。thisやnullptr、ラムダ式もprvalueである。

prvalueの非参照メンバ変数

xvalueと同様にprvalueのメンバ変数もprvalueである。

関数の戻り値

int f(); // prvalue

関数の戻り値は、戻り値が左辺値参照或いは右辺値参照でなければprvalueである。

右辺値参照以外へのキャスト

static_cast<int>x; // prvalue

その他キャストはprvalueである。

その他演算子等の結果

1 + 2; // prvalue
new int(10); // int*型のprvalue

glvalueとrvalue

最後に、lvalue及びxvalueを総称してglvalueという。また、prvalue及びxvalueを総称してrvalueという。xvalueはどちらにも属することが分かる。