参照の配列が違法なのはなぜですか?


149

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

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 &、参照の配列をシミュレートすることができます。


1
配列が有効であっても、生の「8」の値を配列に格納しても機能しません。「intlink value = 8;」を実行した場合、「const int&value = 8;」に変換されるだけなので、恐ろしく死ぬでしょう。参照は変数を参照する必要があります。
Grant Peters、

3
intlink value = 8;動作します。あなたが信じていないかどうかを確認してください。
アレクセイマリストフ2009

7
Alexeyが指摘するように、右辺値をconst参照にバインドすることは完全に有効です。
avakar 2009

1
何をしない仕事はそれがありますoperator=。参照を付け直すことはできません。あなたは本当に、このようなセマンティクスをしたい場合は-私は個人的に、彼らは実用しているしている状況を発見していませんでしたけれども-そしてstd::reference_wrapperそれは実際にポインタを格納したが、基準のように提供して、それを行うための方法だろうoperatorsおよびない再装着が可能。しかし、私はポインタを使用するだけです!
underscore_d

1
operator =はプライベートで実装されていません。別名C ++ 11では= deleteです。
Jimmy Hartzell、2016

回答:


148

標準に関するあなたの質問への回答として、C ++標準§8.3.2/ 4を引用できます。

参照への参照、参照の配列、参照へのポインタはありません。


32
これ以上言うべきことはありますか?
ポリグロット

9
ポインタの配列があり、情報は同じだが処理が異なるのと同じ理由で、参照の配列があるはずです。
neu-rah 2014

6
コンパイラの開発者が拡張機能として参照の配列を許可することを決定した場合、それは成功するでしょうか?または、いくつかの実際の問題があります-おそらくあいまいなコードです-それは、この拡張を定義するのを混乱させすぎるだけでしょうか?
アーロンマクデイド、2015年

23
@AaronMcDaid私が思う本当の理由は-これと同様の議論には非常に欠けています-参照の配列がある場合、要素のアドレスとその参照先のアドレスを明確にする方法は何でしょうか?「配列/コンテナ/何にでも参照を置くことはできません」に対する直接の異議は、唯一のメンバーが参照であるを置くことができるということですstruct。しかし、そうすることで、参照と親オブジェクトの両方に名前を付けることができるようになります。つまり、必要なアドレスを明確に指定できるようになります。当たり前のようです...では、なぜ誰もそれを言わなかったのですか?
underscore_d

95
@polyglot 理由What more is there to say?の根拠?私はすべて標準についてですが、それを引用してそれが議論の終わりであると仮定することは、ユーザーの批判的思考を破壊する確実な方法のように思えます-たとえば、省略の制限を超えたり、人為的な制限。ここでは「標準が言っているから」が多すぎますが、十分ではありません。「次の実用的な理由により、それを言うのは非常に賢明です」。後者を探究することすらせずに、前者だけを言う回答に賛成投票がそれほど熱心に流れる理由はわかりません。
underscore_d

64

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

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

編集: jia3epが注記しているように、宣言の標準セクションでは、参照の配列を明示的に禁止しています。


6
参照は定数ポインタと同じ性質を持っているため、何かを指すためにメモリ(ストレージ)を消費します。
イナザラック2009

7
必ずしも必要ではありません。たとえばローカルオブジェクトへの参照である場合、コンパイラはアドレスの保存を回避できる場合があります。
EFraim 2009

4
必ずしも。構造体の参照は、通常、ある程度のストレージを使用します。多くの場合、ローカル参照はそうしません。どちらの方法でも、厳密な標準的な意味では、参照はオブジェクトではなく、(これは私にとっては初めてでした)名前付き参照は実際には変数ではありません。
CBベイリー

26
はい。ただし、これは実装の詳細です。C ++モデルのオブジェクトは、型付き記憶領域です。参照は明示的にオブジェクトではなく、特定のコンテキストでストレージを使用する保証はありません。
CBベイリー

9
このようにそれを置く:あなたがすることができ、fooのことは何もなく、16人のレフリーの構造体を持っている、と私は、引用文献の私の配列を使用したいと思いますとまったく同じ方法でそれを使用する-あなたは16個のfooの参照へのインデックスではないことを除いて。これは私見いぼの言語です。
greggo

28

これは興味深い議論です。refの配列は明らかに違法ですが、IMHOの理由は、「オブジェクトではない」または「サイズがない」と言うほど単純ではない理由です。配列自体は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; }
};

...そして今私はリファレンスの配列のようにLOTに見えるものを持っています:

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)一連の?を使用して計算を行うことを除いて:(通常のC-pointer-calculation-semantics indexingではなく、インデックスを使用して実行できます、それでもインデックス作成)。私は実際にこれを行うことができる非常に限られた「参照の配列」タイプを見たいです。つまり、参照の配列は、参照であるものの一般的な配列としては扱われず、新しい「参照の配列」になります。 テンプレートで作成)。

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

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

1
これはC ++ 11で行うことができます。これstd::tuple<int&,int&,int&> abcref = std::tie( a,b,c)により、std :: getを使用してコンパイル時の定数でのみインデックスを付けることができる参照の「配列」が作成されます。しかし、あなたはできませんstd::array<int&,3> abcrefarr = std::tie( a,b,c)。たぶん、私には知らない方法があります。そこの特殊書き込むための方法であるべきであるように思わstd::array<T&,N>れることができ、したがって、機能-これを行うためstd::tiearray(...)、1つを返す(または許可std::array<T&,N>含むアドレス=イニシャライザ受け入れる{&V1&V2等})
greggo

あなたの提案はばかげています(IMO)。最後の行では、intがポインターを保持できると想定しています(通常、少なくとも私が作業している場所ではfalse)。しかし、これが、参照の配列が禁止されている理由についての正当な根拠を持つここでの唯一の答えなので、+ 1
Nemo

@Nemoそれは実際に提案として意図されたのではなく、そのようなものの機能がその面で不合理ではないことを単に説明するためです。私の最後のコメントで、c ++には近いものがあることに注意します。また、参照する最後の行は、intへの参照を実装するメモリが、intへのポインタとして有効にエイリアス化できることを前提としています-構造体には、intではなく参照が含まれます-これはtrueになる傾向があります。私はそれが移植可能であると主張していません(または「未定義の動作」ルールからさえ安全です)。これは、すべての?:
状況

けっこうだ。しかし、私はこの考えは「その表面上不条理」であると思います、そしてこれがまさに参照の配列が許されない理由です。配列は、何よりもまずコンテナです。すべてのコンテナには、標準アルゴリズムを適用するためのイテレータタイプが必要です。「Tの配列」の反復子タイプは通常「T *」です。参照の配列のイテレータ型は何でしょうか?これらすべての問題に対処するために必要な言語ハッキングの量は、本質的に役に立たない機能にとってはばかげています。実際、多分私はこれを私自身の答えにするでしょう:-)
Nemo

@Nemoコア言語の一部である必要はないので、イテレータタイプが「カスタム」であることは大したことではありません。さまざまなコンテナタイプにはすべて、イテレータタイプがあります。この場合、逆参照されたイテレータは、配列内の参照ではなくターゲットobjを参照します。これは、配列の動作と完全に一致しています。テンプレートとしてサポートするには、まだ少し新しいC ++の動作が必要な場合があります。非常に単純なstd::array<T,N>、アプリのコードで簡単に定義できるものはstd ::に追加されませんでした。それ= {1,2,3};は「言語ハッキング」でしたか?
greggo 2017

28

編集へのコメント:

より良いソリューションは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;
}

:)新しいポストのための09のヒント表情でこんにちは、これは述べたバック
Stígandr

9
投稿は古いですが、reference_wrapperによるソリューションについて誰もが知っているわけではありません。人々はC ++ 11のSTLとSTDlibについて読む必要があります。
Youw 2014年

これにより、クラスの名前だけではなく、リンクとサンプルコードが提供されreference_wrapperます。
David Stone

12

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


11
参照へのポインタを持つことができないのは事実ですが、これが参照の配列を持つことができない理由ではありません。どちらも参照はオブジェクトではないという事実の両方の症状です。
Richard Corden、

1
構造体は参照のみを含むことができ、サイズはそれらの数に比例します。参照「arr」の配列がある場合、「ポインターから参照」は意味がないため、通常の配列からポインターへの変換は意味がありません。しかし、 'st'がrefを含む構造体である場合、st.refと同じように、単にarr [i]を使用することは意味があります。うーん。しかし、&arr [0]は最初に参照されるオブジェクトのアドレスを提供し、&arr [1]-&arr [0]は&arr [2]-&arr [1]と同じではありません-多くの奇妙さを生み出します。
greggo

11

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

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


うん。サイズがない限り、何かの配列を持つことはできません。参照のサイズはどのくらいですか?
David Schwartz、

4
@DavidSchwartz structその唯一のメンバーがその参照であるを作成し、見つけます。
underscore_d 2016年

3
これは受け入れられた答えであるべきであり、単にOPで標準を投げる愚かな知識人ではありません。
タイソンジェイコブス

たぶんタイプミスがあります。「参照がintと同じ幅ではないと想定」は、「参照がintと同じ幅ではないと想定」する必要があります。変更があるまでのように
zhenguoli 2017

ただし、このロジックでは、参照メンバー変数を使用できませんでした。
タイソンジェイコブス

10

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

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


5

答えは非常に単純で、参照の意味規則とC ++での配列の処理方法に関係していると思います。

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

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

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

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

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

Object objects[1]; // Error: no appropriate default constructor available

1
の配列を作成できます。Objectすべてを初期化することを確認する必要がありますObject objects[1] = {Object(42)};。一方、すべての参照を初期化しても、参照の配列を作成することはできません。
Anton3

4

このテンプレート構造体でかなり近づくことができます。ただし、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}


2

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


3
もしそうなら、コンパイラはrefのみを含む構造体のサイズを計算できますか?
Juster

コンパイラーは参照の物理的なサイズを知っています。これはポインターと同じです。参照は、実際には、意味的に美化されたポインタです。ポインターと参照の違いは、ポインターと比較して作成する可能性のあるバグの数を減らすために、参照にはかなりの数のセマンティックルールが適用されていることです。
Kristupas A.

2

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


1
しかし、この参照をに入れることで、structすべてが魔法のように指定され、明確になり、保証され、確定的なサイズになります。したがって、なぜこの一見完全に人為的な違いがあるのでしょうか?
underscore_d

...もちろん、ラッピングstructが何を与えるかについて実際に考え始めると、いくつかの良い答えが自分自身を示唆し始めます-しかし、私には、これはすべての一般的な人々に対する直接的な挑戦の1つであるにもかかわらず、まだそうしていませんラップされていない参照を使用できない理由の根拠。
underscore_d

2

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


元の質問から次のコードを考えてみます。inta = 1、b = 2、c = 3; int&arr [] = {a、b、c、8}; a、b、cが連続したメモリ位置に存在する可能性はほとんどありません。
Amit Kumar

これは、参照をコンテナに保持するという概念の主な問題からはほど遠いものです。
underscore_d

0

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

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


1
では、structその唯一のメンバーが参照であるを作成すると、完全に合法で予測可能で非アモルファスなものになるのはなぜですか?なぜユーザーはそれをリファレンスでラップして魔法のように配列対応の能力を獲得しなければならないのですか、それとも単なる人工的な制限なのですか?IMOの答えはこれに直接対処していません。私は反論が「ラッピングオブジェクトがラッピングオブジェクトの形式で値のセマンティクス使用できるようにするため、たとえば、要素とレフリーアドレスを明確にするために必要な値の保証を確実にすることができる」と想定します。 ...それはあなたが意味したことですか?
underscore_d

-2

実際、これはCとC ++の構文の混合です。

あなたはすべきいずれかの参照のみがC ++の一部なので、参照のことができない、純粋なC配列を、使用しています。または、C ++の方法で、目的に応じてstd::vectoror std::arrayクラスを使用します。

編集された部分について:structはCの要素ですが、コンストラクターと演算子関数を定義して、C ++にしclassます。したがって、struct純粋なCではコンパイルできません。


あなたのポイントは何ですか?std::vectorまたはstd::array参照を含めることもできません。C ++標準は、コンテナができないことを非常に明確にしています。
underscore_d
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.