web-dev-qa-db-ja.com

参照の配列が不正なのはなぜですか?

次のコードはコンパイルされません。

int a = 1, b = 2, c = 3;
int& arr[] = {a,b,c,8};

C++標準はこれについて何と言っていますか?

以下に示すように、参照を含むクラスを宣言し、そのクラスの配列を作成できることを知っています。しかし、上記のコードがコンパイルされない理由を本当に知りたいです。

struct cintref
{
    cintref(const int & ref) : ref(ref) {}
    operator const int &() { return ref; }
private:
    const int & ref;
    void operator=(const cintref &);
};

int main() 
{
  int a=1,b=2,c=3;
  //typedef const int &  cintref;
  cintref arr[] = {a,b,c,8};
}

struct cintrefの代わりにconst int &を使用して、参照の配列をシミュレートすることができます。

132
Alexey Malistov

標準に関するあなたの質問に答えるC++標準§8.3.2/ 4

参照への参照、参照の配列なし、および参照へのポインタはありません。

137

参照はオブジェクトではありません。独自のストレージはなく、既存のオブジェクトを参照するだけです。このため、参照の配列を持つことは意味がありません。

別のオブジェクトを参照する軽量オブジェクトが必要な場合は、ポインターを使用できます。すべてのstructインスタンスのすべての参照メンバーの明示的な初期化を提供する場合、配列内のオブジェクトとして参照メンバーを持つstructのみを使用できます。参照はデフォルトで初期化できません。

編集: jia3epが指摘しているように、宣言に関する標準セクションには、参照の配列に対する明示的な禁止事項があります。

59
CB Bailey

これは興味深い議論です。明らかにrefの配列は完全に違法ですが、私見の理由は「オブジェクトではない」または「サイズがない」と言うほど単純ではありません。配列自体はC/C++の本格的なオブジェクトではないことを指摘します。それに反対する場合は、配列を「クラス」テンプレートパラメータとして使用してstlテンプレートクラスをインスタンス化し、何が起こるかを確認してください。それらを返したり、割り当てたり、パラメータとして渡したりすることはできません。 (配列パラメーターはポインターとして扱われます)。しかし、配列の配列を作成することは合法です。参照には、コンパイラが計算できるサイズが必要です。参照をsizeof()することはできませんが、参照のみを含む構造体を作成することはできます。参照を実装するすべてのポインターを含むのに十分なサイズになります。すべてのメンバーを初期化せずに、そのような構造体をインスタンス化することはできません。

struct mys {
 int & a;
 int & b;
 int & c;
};
...
int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs.a += 3  ;  // add 3 to ivar1

実際、この行を構造体定義に追加できます

struct mys {
 ...
 int & operator[]( int i ) { return i==0?a : i==1? b : c; }
};

...そして今、私は参照の配列のように見えるものがあります:

int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs[1] = my_refs[2]  ;  // copy arr[12] to ivar2
&my_refs[0];               // gives &my_refs.a == &ivar1

さて、これは実際の配列ではなく、演算子のオーバーロードです。たとえば、sizeof(arr)/ sizeof(arr [0])のように配列が通常行うことは行いません。しかし、完全に正当なC++で、参照の配列に必要なことを正確に実行します。 (a)3つまたは4つ以上の要素を設定するのは苦痛であり、(b)大量の?:を使用して計算を行うことを除いて、これはインデックス作成を使用して実行できます、それでもインデックス付け)。私は実際にこれを行うことができる非常に限られた「参照の配列」タイプを見たいです。つまり参照の配列は、参照であるものの一般的な配列として扱われるのではなく、上記のような内部的に生成されたクラスに効果的にマッピングされる新しい「参照配列」のものになります(ただし、残念ながらca n't make with templates)。

この種の厄介なものを気にしない場合、これはおそらく機能します: '* this'をint *の配列として再キャストし、1つから作成された参照を返します:(推奨されませんが、適切な 'array'動作します):

 int & operator[]( int i ) { return *(reinterpret_cast<int**>(this)[i]); }
28
greggo

編集へのコメント:

より良い解決策はstd::reference_wrapperです。

詳細: http://www.cplusplus.com/reference/functional/reference_wrapper/

例:

#include <iostream>
#include <functional>
using namespace std;

int main() {
    int a=1,b=2,c=3,d=4;
    using intlink = std::reference_wrapper<int>;
    intlink arr[] = {a,b,c,d};
    return 0;
}
26
Youw

配列は暗黙的にポインターに変換可能であり、 ポインターへの参照はC++では無効です

12
EFraim

多くの人がここで言ったように、参照はオブジェクトではありません。それらは単なるエイリアスです。確かに、いくつかのコンパイラはそれらをポインタとして実装するかもしれませんが、標準はそれを強制/指定しません。また、参照はオブジェクトではないため、参照することはできません。配列に要素を保存することは、何らかの種類のインデックスアドレス(つまり、特定のインデックスの要素を指す)があることを意味します。参照の配列を参照できないため、参照の配列を使用できないのはこのためです。

代わりにboost :: reference_wrapperまたはboost :: Tupleを使用してください。または単なるポインタ。

10
navigator

int& arr[] = {a,b,c,8};が与えられた場合、sizeof(*arr)とは何ですか?

それ以外の場所では、参照は単なるものとして扱われるため、sizeof(*arr)は単にsizeof(int)でなければなりません。しかし、これにより、この配列の配列ポインター演算が間違ってしまいます(参照が同じ幅ではないと仮定すると、intになります)。あいまいさを排除するため、禁止されています。

9
PaulMurrayCbr

このテンプレート構造体を使用すると、かなり近いものになります。ただし、TではなくTへのポインターである式で初期化する必要があります。したがって、同様に 'fake_constref_array'を簡単に作成できますが、OPの例( '8')のように右辺値にバインドすることはできません。

#include <stdio.h>

template<class T, int N> 
struct fake_ref_array {
   T * ptrs[N];
  T & operator [] ( int i ){ return *ptrs[i]; }
};

int A,B,X[3];

void func( int j, int k)
{
  fake_ref_array<int,3> refarr = { &A, &B, &X[1] };
  refarr[j] = k;  // :-) 
   // You could probably make the following work using an overload of + that returns
   // a proxy that overloads *. Still not a real array though, so it would just be
   // stunt programming at that point.
   // *(refarr + j) = k  
}

int
main()
{
    func(1,7);  //B = 7
    func(2,8);     // X[1] = 8
    printf("A=%d B=%d X = {%d,%d,%d}\n", A,B,X[0],X[1],X[2]);
        return 0;
}

-> A = 0 B = 7 X = {0,8,0}

4
greggo

答えは非常に簡単で、参照のセマンティックルールとC++での配列の処理方法に関係していると思います。

要するに、参照はデフォルトのコンストラクターを持たない構造体と考えることができるため、すべて同じルールが適用されます。

1)意味的に、参照にはデフォルト値はありません。参照は、何かを参照することによってのみ作成できます。参照には、参照がないことを表す値がありません。

2)サイズXの配列を割り当てるとき、プログラムはデフォルトで初期化されたオブジェクトのコレクションを作成します。参照にはデフォルト値がないため、このような配列の作成は意味的に違法です。

このルールは、デフォルトのコンストラクターを持たない構造体/クラスにも適用されます。次のコードサンプルはコンパイルされません。

struct Object
{
    Object(int value) { }
};

Object objects[1]; // Error: no appropriate default constructor available
3
Kristupas A.

配列に何かを格納する場合、そのサイズを知る必要があります(配列のインデックス付けはサイズに依存するため)。 C++規格によると、参照の配列にインデックスを付けることはできないため、参照にストレージが必要かどうかは指定されていません。

2
Pradyot

すべての会話に追加するだけです。配列はアイテムを格納するために連続したメモリ位置を必要とするため、参照の配列を作成した場合、連続したメモリ位置にあることが保証されないため、アクセスが問題になり、すべての数学演算を適用することさえできませんアレイ。

2
Amit Kumar

参照オブジェクトにはサイズがありません。 sizeof(referenceVariable)と書くと、参照自体のサイズではなく、referenceVariableによって参照されるオブジェクトのサイズがわかります。独自のサイズがないため、コンパイラーは配列に必要なサイズを計算できません。

2
Carl Seleborg

ポインターの配列を検討してください。ポインターは実際にはアドレスです。したがって、配列を初期化するとき、コンピュータに「これらのX番号(他の項目のアドレス)を保持するためにこのメモリブロックを割り当てます」と同様に伝えます。次に、ポインターの1つを変更すると、それが指すものが変更されるだけです。それは、それ自体が同じ場所にある数値アドレスです。

参照はエイリアスに似ています。参照の配列を宣言する場合、基本的にはコンピューターに、「散在するこれらすべての異なるアイテムで構成されるこの不定形のメモリの割り当て」と伝えます。

0
Dan Tao