web-dev-qa-db-ja.com

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

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

_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の型はiと同じであるべきだと思う:iが_int const_であるとき、なぜriconstではないのか?

85
Troskyvs

これは直感に反するように思えるかもしれませんが、これを理解する方法は、特定の点でreferencesが処理されることを理解することだと思いますsyntactically like pointers

これはpointerに対して論理的と思われます:

_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
_

これは論理的です。なぜなら、pointer objectではなく、const(他の場所を指すようにできる)が指しているのがオブジェクトだからです。

したがって、pointerconstness自体がfalseとして返されていることが正しくわかります。

pointer自体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
_

そして、私はreferenceとの構文上の類似性を見ていると思います。

しかし、referencesは、特にone重大な点で、ポインタと意味的に異なります。バインドされた別のオブジェクトへの参照rebindは許可されません。

referencespointersと同じ構文を共有しますが、ルールは異なるため、言語によってreference自体を宣言できません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;
}
_

言語規則がreferencepointer could( const)と宣言されていない場合。

質問に答えるために:

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

あなたの例では、構文はpointerを宣言した場合と同じ方法でconstを参照するようにします。

正しくも誤って、reference自体をconstにすることはできませんが、そうであれば次のようになります。

_int const& const ri = i; // not allowed
_

Q)参照が名前/変数にバインドされると、このバインドを変更できないこと、バインドされたオブジェクトを変更することがわかります。だから私はriの型がiと同じであるべきだと思う:iが_int const_であるとき、なぜriconstではないのか?

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

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

52
Galik

なぜ「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修飾子が導入される場合を除き、Cv修飾参照は不正な形式です。 、その場合、cv修飾子は無視されます。

is_const<decltype(ri)>::valueは、false(参照)がconst修飾型ではないため、riを返します。あなたが言ったように、初期化後に参照を再バインドすることはできません。これは、参照が常に「const」であることを意味します。

54
songyuanyao

探している値を取得するには、 std::remove_reference を使用する必要があります。

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

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

18
chema989

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

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

参照型はそれだけ-型-であるため、他の型(特にポインター型)との対称性のためにすべてのconst修飾子を要求するのは理にかなっているかもしれませんが、これは非常に手間がかかります。

C++にデフォルトで不変オブジェクトがあり、did n'tmutableになりたい場合は、constキーワードを要求する場合、これは簡単だったでしょう。単に、プログラマがmutableを参照型に追加することを許可しないでください。

現状では、それらは資格なしでは不変です。

そして、それらはconstで修飾されていないので、おそらくmoreis_const参照型でtrueを生成します。

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

これは、C++の癖/機能です。参照は型とは考えませんが、実際には型システムに「座る」。これは厄介なようですが(参照が使用されると、参照セマンティクスが自動的に発生し、参照が「邪魔にならない」と考えられます)、参照がタイプ。

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

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

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

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

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

riを使用すると、参照は透過的に解決されるため、riは「i」のように見え、「エイリアス」と呼ばれます。ただし、型システムでは、riには実際に「reference to _const int_」という型があります。

なぜ参照型なのですか?

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

_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番目のクラスの市民」として型システム内にあります。参照へのポインターやそれらの配列を宣言するなど、参照で「実行」できない特定のことがあります。しかし、それは、それらが型ではないという意味ではありません。彼らはそれが理にかなっている方法でタイプではありません。

これらの第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-修飾可能型: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_と配列からポインターへの変換を抑制し、サイズを計算するための配列型自体

5
Kaz