l値とr値

すべての式の値は、l値またはr値のいずれかに分類される。2つを区別する簡単な方法は、l値を実際の変数(配列や連想配列の要素を含む)とし、r値を式の一時的な結果(リテラルを含む)と考えることだ。

これを実例で説明すると、以下の最初のwriteln()式はl値のみを使用し、もう一方の式はr値のみを使用している。

import std.stdio;

void main() {
    int i;
    immutable(int) imm;
    auto arr = [ 1 ];
    auto aa = [ 10 : "ten" ];

    /* 以下の引数はすべてl値だ。 */

    writeln(i,          // 変更可能な変数
            imm,        // 不変の変数
            arr,        // 配列
            arr[0],     // 配列要素
            aa[10]);    // 連想配列要素
                        // など。

    enum message = "hello";

    /* 以下の引数はすべてr値だ。 */

    writeln(42,               // リテラル
            message,          // 明示的な定数
            i + 1,            // 一時的な値
            calculate(i));    // 関数の戻り値
                              // など。
}

int calculate(int i) {
    return i * 2;
}
D
lvalue_rvalue.1
r値の制限

l値と比較して、r値には次の3つの制限がある。

r値にはメモリアドレスがない

l値には参照できるメモリ位置があるが、r値にはない。

例えば、次のプログラムでは、r値式a + bのアドレスを取得することはできない。

import std.stdio;

void main() {
    int a;
    int b;

    readf(" %s", &a);          // ← コンパイルする
    readf(" %s", &(a + b));    // ← コンパイルエラー
}
D
lvalue_rvalue.2
エラー: a + b はlvalueではない
r値には新しい値を代入できない

変更可能であれば、l値には新しい値を代入できるが、r値には代入できない。

a = 1;          // ← コンパイルできる
(a + b) = 2;    // ← コンパイルエラー
D
エラー: a + b はlvalueではない
R値は関数に参照として渡すことはできない

l値は、参照によってパラメータを受け取る関数に渡すことができるが、r値は渡すことができない。

void incrementByTen(ref int value) {
    value += 10;
}

// ...

    incrementByTen(a);        // ← コンパイルできる
    incrementByTen(a + b);    // ← コンパイルエラー
D
エラー: 関数deneme.incrementByTen (ref int value)は、
引数の型(int)を使用しては呼び出せない

この制限の主な理由は、refパラメータを取る関数は、r値が利用できなくなった後も、その参照を後で使用するために保持できるからだ。

C++などの言語とは異なり、Dでは、関数が引数を変更しない場合でも、r値を関数に渡すことはできない。

void print(ref const(int) value) {
    writeln(value);
}

// ...

    print(a);        // ← コンパイルできる
    print(a + b);    // ← コンパイルエラー
D
エラー: 関数deneme.print (ref const(int) value)は、
引数の型(int)を使用しては呼び出せない
l値とr値の両方を受け入れるauto refパラメータの使用

前の章で述べたように、関数テンプレートauto refパラメータは、l値とr値の両方を受け取ることができる。

引数がl値の場合、 auto refは参照を意味する。一方、r値は関数に参照として渡すことができないため、引数がr値の場合、はコピーを意味する。コンパイラがこれら2つの異なるケースに対して異なるコードを生成するには、関数はテンプレートでなければならない。

テンプレートについては、後の章で説明する。ここでは、強調表示された空の括弧が、以下の定義を関数テンプレートにしていることを覚えておいてほしい。

void incrementByTen()(auto ref int value) {
    /* 警告: 引数がr値の場合、パラメータはコピーになる可能性がある。
     * つまり、以下の変更は
     * 呼び出し元からは確認できない可能性がある。 */

    value += 10;
}

void main() {
    int a;
    int b;

    incrementByTen(a);        // ← l値; 参照によって渡される
    incrementByTen(a + b);    // ← r値; コピーされる
}
D
lvalue_rvalue.3

パラメータがl値であるかr値であるかは、__traits(isRef)static ifと組み合わせて使用することで判断できる。

void incrementByTen()(auto ref int value) {
    static if (__traits(isRef, value)) {
        // 'value'は参照によって渡される
    } else {
        // 'value'はコピーされる
    }
}
D

static if__traitsについては、後述の条件付きコンパイルの章で説明する。

用語

"l値"および"r値"という名称は、これら2種類の値の特性を正確に表しているわけではない。最初の文字lおよびrは、代入演算子の左側および右側の式を指す、leftおよびrightから来ている。

l値とr値は、一般的にl値もr値も代入演算子の両側に置けるため、用語として紛らわしい。

// 左側にr値の'a + b'、右側にl値の'a':
array[a + b] = a;
D