C++の参照を使ってもcall by referenceにはならない

* はじめに *
ここ最近(昔から?)、C言語の本や記事などに
「ポインタを使うことで参照呼び(call by reference)が…」
なんて書いてあると、
「C言語には値呼び(call by value)しかないくぁwせdrftgyふじこlp…」
などというツッコミで荒れるみたいでが、
C++の本や記事に「参照呼び」と書いてあることで荒れるところを
(少なくても私は)見たことがないのが不思議なので、このエントリを書きます。

* 参照でcall by reference? *
C++でこんなコードを書くと、

int f(int& x) { ... }
int g() {
  ...
  f(a + 1);
  ...
}

こんな感じのエラーが出ますと。

error: invalid initialization of non-const reference of type ‘int&’ from a temporary of type ‘int’
error: in passing argument 1 of ‘int f(int&)’

C++がこういう仕様であること自体には何も問題はないと思います。
でも、これがエラーになるということは、C++の参照を使っても、
call by referenceにはなっていないということです。

* call by referenceの定義 *
私が説明をつらつらと書いても納得してもらえなさそうなので、
有名どころから引用します。

参照呼び

引数を参照呼び(番地呼びまたは記憶場所呼びともいう)にすると,一般には,
呼出側の手続きは,呼び出される側に対して,次のように実引数の記憶場所
の番地を渡す.

  1. 実引数が名前または左辺値をもつ式であれば,その左辺値自身を渡す.
  2. しかし,実引数がa+bや2のような式であれば,左辺値はないので,式を
    評価して,その値を新しい記憶場所に格納し,その番地を渡す.

(『コンパイラII 原理・技法・ツール』 初版 517ページ)

ついでにもう一冊

This parameter-passing mechanism is called call-by-reference. If an operand is simply a variable reference, a reference to the variable’s location is passed. The formal parameter of the procedure is then bound to this location. If the operand is some other kind of expression, then the formal parameter is bound to a new location containing the value of the operand, just as in call-by-value.

(“Essentials of Programming Languages” second edition, p.109)

お分かりいただけたでしょうか。
call by referenceでは、上記プログラムのようなa+1みたいな式も渡せないといけないのです。
つまり、C++の参照をつかってもcall by referenceにはならないということですね。
ちなみに、その昔FORTRANではcall by referenceしかなかったので、a+0のような式を書くことで、
call by valueをシミュレートするという技があったらしいです。
今時のFORTRANがどうなっているかは知りません。

* おわりに *
Javaも「参照」という言葉を使っているためか、
時折「Javaの参照呼びが云々」と書かれることがありますが、
これには「それは参照の値呼びだ!」という、
分かりやすく分かりにくいツッコミが入るところを何度か見たことがあります。
しかし、冒頭にも書いたとおり、C++の参照に関してはツッコミが入ったところを見たことがありません。
単に私が見ているところが悪いだけなのか、それともC++特有の文化があるのか(たとえばC++の仕様書に”call by reference”と書いてあるとか)。

謎です。

4 Responses to “C++の参照を使ってもcall by referenceにはならない”

  1. Flast より:

    C++にはオブジェクトに関していくつか規定があります。大きく分けてlvalue(左辺値)とrvalue(右辺値)です。
    式を評価したあとの値はさらに別扱いでxvalueとなり、prvalue(純粋なrvalue)かlvalueからなります。
    C++03の仕様ではnon-const referenceに渡せるのはnon-constなlvalueのみです。

    int a;
    があるとき、式 a はnon-const lvalue reference(lvalueへの参照)を返します。
    一方a+1は一時オブジェクトを生成しなければなりません。この一時オブジェクトというのはprvalueであると規定されています。

    ここでint f(int &)は引数にnon-const lvalue referenceを要求しています。そうするとf(a+1)という式はnon-const lvalue referenceを要求しているところにprvalueを渡そうとしているので違法です。

    ところでnon-constをやたら強調しましたが、prvalueの扱いについて例外があり、const lvalue referenceにはprvalueを渡すことができます。
    つまりint f(const int &)に対しf(a+1)は合法です。

    このC++03の規定では一時オブジェクトの扱いが雑(というか面倒?)であったこともあり、次期標準の通称C++0xではrvalue reference(rvalueへの参照)が導入されました。
    これはf(int &&)のように書き、この関数への呼び出しf(a+1)は合法です。
    一方rvalueはlvalueでは無いのでf(int &&)しか存在しない場合、f(a)は違法です。
    (templateが入ってくると実はさらに挙動が変わります。

    つまりややこしいので誰も突っ込めないのです。
    私自身この説明で規格通りになってるか自信はありませんが、あまり深入りしないのが正解な気がします。闇の軍団とか怖いですし。

  2. Flast より:

    あっ、微妙に間違えてます… 式を評価したあとのくだりは無視してください…

  3. m0h1can より:

    “式を評価して、その値/the value of the operand”の部分で、言語が定義するところの「評価」として解釈するならば、C++のコンパイラが言っているようにinvalidな初期化を弾いているだけで、参照呼びで無いかどうかは分からないように思います。
    (まあ限定的な参照渡しだとか仕様に明記したとして、(C++er以外の)皆が参照渡しだと納得するかどうかは微妙なラインだわね..)

    ちなみにFORTRANで常用された a+0 等の値渡しイディオムは現在も有効ですが、Fortran90以降は変数属性に value を指定することで値渡しとして扱う事が可能であり推奨です。

  4. zick より:

    > Flastさん
    C++0xにはrvalue referenceなんてものがあるんですか。
    面白いけど、ただでさえ複雑なC++がさらに複雑になるんですね。
    恐ろしい。

    > m0h1canさん
    a+0のイディオムは(非推奨とはいえ)いまだに使えるんですね。

Leave a Reply