読者です 読者をやめる 読者になる 読者になる

参照の配列は何故作れないか

さて問題です。次のコードの実行結果はどうなるでしょうか?

#include <iostream>

using namespace std;

int main(){
  int a = 1, b = 2, c = 3;
  int &n[3] = {a, b, c};

  for(int i = 0; i < 3; ++i)
    cout << n[i] << ' ' ;
  cout << endl;

  return 0;
}

見出しを見た皆様のお察しの通り、正解は、「そもそもコンパイルされない」です。

code.cpp: In function `int main()':
code.cpp:7: error: declaration of `n' as array of references
code.cpp:10: error: `n' undeclared (first use this function)
code.cpp:10: error: (Each undeclared identifier is reported only once for each function it appears in.)

このように、C++では参照の配列を作成することは出来ません。
しかし、一体何故でしょうか? 参照の配列によって簡潔に記述できるような処理に出くわすこともままありますし、参照の配列を作ることが原理的に不可能であるとは到底思えません。 それなのに、何故禁止されているのでしょう?

やっぱりマズい

ググっても納得のいく答えが得られなかったので、成田さんに訊いてみました。
すると、「アドレスが連続でなくなることがあるからではないか」との答えが返ってきました。 どういうことでしょうか。

仮に、以下のように配列と参照の配列を定義することができたとします。

int  array[] = {1, 2, 3};
int &refArray[] = {array[0], array[2]};

次に、こんなコードを考えてみます。

int* p = refArray;
cout << *p << endl;

ここでは refArray[0] ( = array[0]) の値である 1 が出力されると期待するのが理に適っています。 ここまでは何の問題もありません。
それでは、次の例を見てみましょう。

int* q = p;
q = q + 1;
cout << *q << endl;

ここでは何が出力されるべきでしょうか?
refArray[1]、つまり array[2] が出力されると考えてみましょう。
これは、q - p を考えたときに不自然であることに気付きます。 qarray[2] を、parray[0] を指しているのですから、q - p は 2 となるはずです。 q = p + 1 としたにもかかわらず、アドレスが2つも動いてしまいました。
それどころか、もし refArray[1]array[2] でなく全く別の変数を指していた場合、q はもっとアクロバティックに動いていたであろうことは想像に難くありません。
ただのポインタ変数である q にこんな挙動をされては困ってしまいます。

それでは、array[1] が出力されると考えてみましょう。 それはつまり、*(refArray + 1)array[1] であるということです。
しかし、これはこれで不自然です。 配列では一般的に *(a + n)a[n] とが同値でなければなりませんが、そのようになっていません。 refArray[1]array[2] を指しています。

どちらに解釈しても、納得のいかない挙動が生まれてしまいます。 また、これら以外に都合のよさそうな解釈も見当たりません。

そういうわけで、参照の配列は作ることが出来ないのだと思われます。
参照へのポインタを作ることが出来ないのも、同様の理由によるものでしょう。

main(){
  int   n      = 3;
  int  &ref    = n;
  int &*ptrRef = &ref;
}
code.cpp: In function int main()':<br /> code.cpp:4: error: cannot declare pointer toint&'

代替案

それでは、参照の配列に近いものが欲しいときにはどうすればいいのでしょうか。

素直にポインタを使うのが一番楽だと思います。

int  array[] = {1, 2, 3};
int* ptrArray[] = { &array[0], &array[2] };

cout << ptrArray[0] << ' ' << ptrArray[1] << endl; // => 1 3

配列の参照なら作れる

また、全くの別物ですが、配列への参照は作ることが出来ます。
以下のようにすれば、関数の引数として配列への参照を渡すことが出来ます。

void f(int (&refArray)[3])

ただし、この方法は普通に配列を渡すのと何ら変わりません。 メリットもデメリットもありません。

また、配列への参照を宣言することも出来るようです。 が、具体的な方法を私は知りません。
gcc の拡張構文を使って以下のように書けることから、少なくとも gcc で可能であることだけは分かります。

int n[] = {1, 2, 3};
__typeof(n) & r = n;

cout << r[0] << ' ' << r[1] << ' ' << r[2] << endl; // => 1 2 3

拡張構文を使わない書き方をご存知の方がいましたら、教えていただけるとうれしいです。

田山