C ++で参照が「const」ではないのはなぜですか?


86

「const変数」は、一度割り当てられると、次のように変数を変更できないことを示しています。

int const i = 1;
i = 2;

上記のプログラムはコンパイルに失敗します。gccはエラーでプロンプトを出します:

assignment of read-only variable 'i'

問題ありません、私はそれを理解できますが、次の例は私の理解を超えています:

#include<iostream>
using namespace std;
int main()
{
    boolalpha(cout);
    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;
    int const &ri = i;
    cout << is_const<decltype(ri)>::value << endl;
    return 0;
}

出力します

true
false

奇妙な。参照が名前/変数にバインドされると、このバインドを変更することはできず、バインドされたオブジェクトを変更することがわかっています。だから私はのタイプがri同じでなければならないと思いますiiが、int constであるとき、なぜそうではriないのconstですか?


3
また、boolalpha(cout)非常に珍しいです。std::cout << boolalpha代わりに行うことができます。
isanae 2016年

6
riと見分けがつかない「エイリアス」であることはこれだけiです。
kaz 2016年

1
これは、参照が常に同じオブジェクトにバインドされているためです。iも参照ですが、歴史的な理由から、明示的な方法でそのように宣言することはありません。したがってi、ストレージをri参照する参照であり、同じストレージを参照する参照です。しかし、その間に自然の中で違いはありませんi とはri。参照のバインドは変更できないため、として修飾する必要はありませんconst。そして、@ Kazコメントは、検証された回答よりもはるかに優れていると主張させてください(ポインターを使用して参照を説明しないでください。refは名前であり、ptrは変数です)。
ジャン=バティスト・Yunès

1
素晴らしい質問です。この例を見る前に、私もこの場合is_constに戻ることを期待していtrueました。私の意見では、これはなぜconst根本的に後方にあるのかを示す良い例です。「可変」属性(a la Rustのmut)は、より一貫性があるように見えます。
カイルストランド2016年

1
おそらく、タイトルを「なぜis_const<int const &>::value間違っているのか」に変更する必要があります。または類似; 型特性の振る舞いについて尋ねる以外に、質問の意味を理解するのに苦労しています
MM

回答:


52

これは直感に反するように思えるかもしれませんが、これを理解する方法は、特定の点で、参照構文的ポインターのように扱われることを理解することだと思います

これはポインタにとって論理的なようです:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

出力:

true
false

constであるのはポインタオブジェクトではなく(他の場所を指すようにすることができます)、ポイントされているオブジェクトであることがわかっているため、これは論理的です。

したがって、ポインタ自体の定数がとして返されることが正しくわかりますfalse

ポインタ自体constを作成したい場合は、次のように言う必要があります。

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* const ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

出力:

true
true

したがって、参照との構文上の類似性が見られると思います

ただし、参照は、特に1つの重要な点でポインターと意味的に異なります。一度バインドされると、別のオブジェクトへの参照を再バインドすることはできません。

したがって、参照ポインタと同じ構文を共有しますが、ルールが異なるため、言語によって、参照自体を次のconstように宣言できなくなります

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const& const ri = i; // COMPILE TIME ERROR!
    cout << is_const<decltype(ri)>::value << endl;
}

言語規則により、ポインターと同じように参照がリバウンドされない場合(宣言されていない場合)は必要ないように見えるため、これを行うことは許可されていないと思います。const

だから質問に答えるために:

Q) C ++で「参照」が「定数」ではないのはなぜですか?

あなたの例では、構文は、ポインタconstを宣言した場合と同じように参照されるようにします

正しいか間違っているかを問わず、参照自体を作成することは許可されていませんが、参照constすると次のようになります。

int const& const ri = i; // not allowed

Q)参照が名前/変数にバインドされると、このバインドを変更することはできず、バインドされたオブジェクトを変更することがわかっています。だから私はのタイプriが同じでなければならないと思いますiiが、int constであるとき、なぜそうでriはないのconstですか?

なぜされdecltype()たオブジェクトに転送されないrefereceがにバインドされていますか?

これはポインタとの意味的同等性のためであり、おそらくdecltype()(宣言された型)の機能は、バインディングが行われる前に宣言されたものを振り返ることであると思います。


13
「参照は常にconstであるため、constにすることはできません」と言っているように聞こえますか?
ruakh 2016年

2
「というのは本当かもしれ意味的にそれらが結合された後、それらにすべてのアクションは、彼らが参照するオブジェクトに転送されている」が、OPを適用することを期待していたdecltypeとしても、それがなかったことがわかりました。
ruakh 2016年

10
ここには多くの曲がりくねった仮定とポインターへの疑わしい適用可能なアナロジーがありますが、sonyuanyaoが行ったような標準をチェックすると、問題が必要以上に混乱すると思います。参照はcv適格タイプではないため、することはできないためconst、をstd::is_const返す必要がありfalseます。彼らは代わりにそれが戻らなければならないことを意味する言葉遣いを使うことができたtrueが、彼らはしなかった。それでおしまい!「私が推測する」、「私が推測する」などのポインタに関するこれらすべてのことは、実際の説明を提供しません。
underscore_d 2016年

1
@underscore_dこれはおそらく、ここのコメントセクションよりもチャットの方が良い質問ですが、参照についてのこの考え方からの「不必要な混乱」の例はありますか?それらをそのように実装できるとしたら、それらをそのように考えることの問題は何ですか?ので、(私はこの質問の数を考えていないdecltypeランタイム操作ではない、実際には適用されません正しいかどうか、という考え「実行時に、参照はポインタのように振る舞う」ので、。
カイルストランド

1
参照も、構文的にはポインターのようには扱われません。宣言して使用する構文は異なります。だから私はあなたが何を意味するのかさえわかりません。
ジョーダンメロ2016年

55

「ri」が「const」ではないのはなぜですか?

std::is_const タイプがconst修飾されているかどうかをチェックします。

Tがconst修飾型(つまり、constまたはconst volatile)の場合、trueに等しいメンバー定数値を提供します。その他のタイプの場合、値はfalseです。

ただし、参照をconst修飾することはできません。参考文献[dcl.ref] / 1

typedef-name([dcl.typedef]、[temp.param])またはdecltype-specifier([dcl.type.simple])を使用してcv-qualifiersが導入された場合を除いて、Cv修飾参照は形式が正しくありません。 、この場合、cv-qualifiersは無視されます。

だから、is_const<decltype(ri)>::value戻りますfalsebecuase ri(リファレンス)const修飾型ではありません。あなたが言ったように、初期化後に参照を再バインドすることはできません。つまり、参照は常に「const」ですが、const修飾参照またはconst非修飾参照は実際には意味がない場合があります。


5
受け入れられた答えのすべての厄介な仮定ではなく、標準にまっすぐに、したがって要点にまっすぐに。
underscore_d

5
@underscore_dこれは、opが理由を尋ねていることを認識するまでは良い答えです。「そう言っているので」は、質問のその側面にはあまり役に立ちません。
simpleuser 2016年

5
@ user9999999他の答えも、実際にはその側面に答えていません。参照をリバウンドできない場合、なぜis_const戻らないのtrueですか?その答えは、ポインタがオプションで再封可能である方法に類似性を引き出そうとしますが、参照はそうではありません-そうすることで、同じ理由で自己矛盾につながります。標準を書いている人たちによるやや恣意的な決定を除いて、どちらの方法でも本当の説明があるかどうかはわかりません。時にはそれが私たちが望むことができる最高のものです。したがって、この答え。
underscore_d 2016年

2
もう1つの重要な側面は、関数decltypeないため、参照先のオブジェクトではなく、参照自体に直接作用することだと思います。(これはおそらく「参照は基本的にポインタ」の回答に関連していますが、それでもこの例を混乱させる原因の一部であるため、ここで言及する価値があります。)
Kyle Strand

3
@KyleStrandからのコメントをさらに解明するために:decltype(name)一般的なものとは異なる動作をしdecltype(expr)ます。したがって、たとえば、decltype(i)は宣言されたタイプでiありconst intdecltype((i))はですint const &
ダニエルシェプラー2016年

18

あなたがstd::remove_reference探している価値を得るために使う必要があります。

std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;

詳細については、この投稿を参照してください。


7

マクロがないのはなぜconstですか?関数?リテラル?タイプの名前は?

const 物事は不変のもののサブセットにすぎません。

参照型はまさにそれであるため—型—const他の型(特にポインター型)との対称性のためにそれらすべてに-qualifierを要求することは理にかなっているかもしれませんが、これは非常にすぐに面倒になります。

C ++が必要、デフォルトで不変オブジェクトを持っていた場合mutable、あなたは何にキーワードをなかったことにしたいconst、これは簡単だったでしょう:単にプログラマが追加することはできませんmutable参照型に。

現状では、資格がなくても不変です。

また、これらはconst修飾されていないため、参照型でtrueを生成する方が混乱する可能性がありis_constます。

特に、参照を変更するための構文が存在しないという単なる事実によって不変性が強制されるため、これは合理的な妥協案であると思います。


5

これはC ++の癖/機能です。参照を型とは考えていませんが、実際には型システムに「座っています」。これは厄介に思えますが(参照が使用されると、参照のセマンティクスが自動的に発生し、参照が「邪魔にならない」)、参照が外部の個別の属性としてではなく型システムでモデル化される理由がいくつかあります。タイプ。

まず、宣言された名前のすべての属性が型システムに含まれている必要はないことを考えてみましょう。C言語から、「ストレージクラス」と「リンケージ」があります。名前はとして導入できますextern const int ri。ここで、externは静的ストレージクラスとリンケージの存在を示します。タイプはちょうどconst intです。

C ++は明らかに、式には型システムの外部にある属性があるという概念を取り入れています。この言語には、式が示すことができる非型属性の増加する数を整理する試みである「値クラス」の概念があります。

しかし、参照はタイプです。どうして?

以前は、C ++チュートリアルで、宣言はタイプを持っているものとしてconst int &ri導入さriれたがconst int、セマンティクスを参照していると説明されていました。その参照セマンティクスはタイプではありませんでした。それは単に名前と保管場所の間の異常な関係を示す一種の属性でした。さらに、参照が型ではないという事実は、型構築構文で許可されていても、参照に基づいて型を構築できない理由を合理化するために使用されました。たとえば、配列または参照へのポインタは使用できません:const int &ari[5]およびconst int &*pri

しかし実際には、参照型であるため、decltype(ri)修飾されていない参照型ノードを取得します。を使用して基になる型に到達するには、型ツリーでこのノードを通過する必要がありますremove_reference

を使用するriと、参照は透過的に解決されるため、ri「見た目も使い心地もi」、その「エイリアス」と呼ぶことができます。ただし、型システムでriは、実際には「参照 」である型がありconst intます。

なぜ参照型なのですか?

参照が型ではない場合、これらの関数は同じ型であると見なされることを考慮してください。

void foo(int);
void foo(int &);

それは、ほとんど自明の理由であるはずがありません。それらが同じタイプである場合、それはどちらかの宣言がどちらの定義にも適していることを意味するため、すべての(int)関数が参照を取得している疑いがあります。

同様に、参照が型でない場合、これら2つのクラス宣言は同等になります。

class foo {
  int m;
};

class foo {
  int &m;
};

1つの翻訳ユニットが1つの宣言を使用し、同じプログラム内の別の翻訳ユニットが他の宣言を使用するのは正しいことです。

事実、参照は実装の違いを意味し、 C ++の型はエンティティの実装、いわばビット単位の「レイアウト」に関係しているため、それを型から分離することは不可能です。2つの関数のタイプが同じである場合、それらは同じバイナリ呼び出し規約で呼び出すことができます。ABIは同じです。2つの構造体またはクラスが同じタイプである場合、それらのレイアウトは同じであり、すべてのメンバーへのアクセスのセマンティクスも同じです。参照の存在は型のこれらの側面を変えるので、それらを型システムに組み込むことは簡単な設計上の決定です。(ただし、ここで反論に注意してください。構造体/クラスのメンバーはである可能性がありstatic、これによって表現も変更されますが、タイプではありません!)

したがって、参照は「二級市民」として型システムにあります(ISO Cの関数や配列とは異なります)。参照へのポインタやそれらの配列を宣言するなど、参照で「実行」できない特定のことがあります。しかし、それはそれらがタイプではないという意味ではありません。それらは、それが理にかなっている方法でタイプされていないだけです。

これらすべての第2クラスの制限が必須というわけではありません。参照の構造があるとすると、参照の配列が存在する可能性があります。例えば

// fantasy syntax
int x = 0, y = 0;
int &ar[2] = { x, y };

// ar[0] is now an alias for x: could be useful!

これはC ++で実装されていないだけで、それだけです。ただし、参照から持ち上げられたポインタは参照されたオブジェクトに移動するだけなので、参照へのポインタはまったく意味がありません。参照の配列がない理由として考えられるのは、C ++の人々が、配列をCから継承された一種の低レベルの機能であり、修復不可能な多くの方法で壊れていると考えており、配列に触れたくないためです。新しいものの基礎。ただし、参照の配列が存在することで、参照が型である必要があることを明確に示すことができます。

const-qualifiable種類:あまりにも、ISO C90で見られます!

いくつかの回答は、参照がconst修飾子をとらないという事実をほのめかしています。宣言const int &ri = iは修飾された参照を作成しようとさえていないので、それはむしろ赤いニシンconstです:それはconst修飾された型への参照です(それ自体はそうではありませんconst)。const in *ri何かへのポインタを宣言するのと同じですconstが、そのポインタ自体はそうではありませんconst

とは言うものの、参照がconst修飾子自体を運ぶことができないのは事実です。

しかし、これはそれほど奇妙なことではありません。ISO C 90言語でも、すべてのタイプがであるとは限りませんconst。つまり、配列はできません。

まず、const配列を宣言するための構文が存在しません:int a const [42]エラーです。

ただし、上記の宣言が実行しようとしていることは、中間を介して表現できますtypedef

typedef int array_t[42];
const array_t a;

しかし、これは見た目どおりには機能しません。この宣言では、aどちらがconst修飾されるかではなく、要素です!つまり、a[0]あるconst intが、a単に「int型の配列」です。したがって、これには診断は必要ありません。

int *p = a; /* surprise! */

これは行います:

a[0] = 1;

繰り返しますが、これは、参照がある意味で、配列のように型システムの「第2クラス」であるという考えを強調しています。

配列にも参照のように「目に見えない変換動作」があるため、類推がさらに深く当てはまることに注意してください。プログラマーが明示的な演算子を使用しなくても、式が使用されたかのように、識別子はa自動的にint *ポインターに変わります&a[0]。これは、参照をri一次式として使用するときに、参照iがバインドされているオブジェクトを魔法のように示す方法に似ています。これは、「配列からポインタへの減衰」のような別の「減衰」です。

また、「配列からポインタへ」が「配列はCおよびC ++の単なるポインタである」と誤って考えてしまうことに混乱してはならないのと同じように、参照が独自​​のタイプを持たない単なるエイリアスであると考えてはなりません。

場合decltype(ri)、その参照先オブジェクトへの参照の通常の変換を抑制し、これは大差ないsizeof aアレイ・ツー・ポインタ変換を抑制し、そして上で動作するアレイ型自体のサイズを計算します。


ここには多くの興味深く役立つ情報(+1)がありますが、それは多かれ少なかれ「参照は型システムにある」という事実に限定されています。これはOPの質問に完全に答えるものではありません。この回答と@songyuanyaoの回答の間には、状況を理解するのに十分な情報があると思いますが、完全な回答というよりは、回答に必要な背景のように感じます。
カイルストランド2016年

1
また、この文は強調する価値があると思います。「riを使用すると、参照は透過的に解決されます...」1つの重要なポイント(コメントで言及しましたが、これまでのところどの回答にも表示されていません) )は、この透過的な解決を実行しdecltype ません(これriは関数ではないため、説明する意味で「使用」されません)。これは、型システムに焦点を当てる全体に非常にうまく適合します。重要な接続decltype、型システム操作であるということです。
カイルストランド2016年

うーん、配列に関するものは接線のように感じますが、最後の文は役に立ちます。
カイルストランド2016年

.....この時点で私はすでにあなたに賛成しており、私はOPではないので、あなたは私の批評を聞いても実際には何も得たり失ったりしていません....
カイルストランド2016年

明らかに、簡単な(そして正しい答え)は私たちに標準を示しているだけですが、その一部が仮定であると主張する人もいるかもしれませんが、私はここでの背景の考え方が好きです。+1。
スティーブキッド2016年

-5

const X&x」は、xがXオブジェクトをエイリアスすることを意味しますが、xを介してそのXオブジェクトを変更することはできません。

そして、std :: is_constを参照してください。


これは質問に答えようとさえしません。
bolov 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.