37

私は C++ Primer を読んでいましたが、式がいつオブジェクト型を生成し、いつオブジェクトへの参照型を生成するのかよくわかりませんでした。

本から引用します:

  1. 変数ではない式に decltype を適用すると、その式が生成する型が得られます。
  2. 一般的に言えば、decltype は、割り当ての左側に立つことができるオブジェクトを生成する式の参照型を返します。

以下のコードを検討してください。

int i = 3, *ptr = &i, &ref = i;
decltype(ref + 0) j;

上記のコードでは、式 "ref + 0" は、ref が参照するオブジェクトの値 i と 0 を加算する固有の操作になります。したがって、最初の規則に従って、式は int 型を生成します。しかし、2 番目の規則に従うと、式は代入の左側に立つことができるオブジェクトの型 (この場合は int) を生成するため、decltype は int(int&) 型への参照を生成するべきではありませんか?

この本には、次のコードについても書かれています

decltype(*ptr) k;

k の型は int& であり、式の結果の型である int ではありません。

また、以下のコードのような代入式についても述べています

decltype(a = b) l;

l は、代入操作の左側にオブジェクトへの参照の型を持ちます。

どの式がオブジェクト型を生成し、どの式がオブジェクト型への参照を生成するかをどのように知るのでしょうか?

4

2 に答える 2

50

これらの概念を理解することは、形式化せずに容易ではありません。この入門書はおそらくあなたを混乱させたくないので、「lvalue」、 「 rvalue 」 、「xvalue」などの用語の導入を避けています。残念ながら、これらは がどのように機能するかを理解するための基本的なdecltypeものです。

まず第一に、評価された式の型は決して参照型ではなく、const非クラス型の最上位修飾型 ( int constor などint&) でもありません。int&式の型がorであることが判明した場合、それ以上の評価の前にint constすぐに変換されます。int

これは、C++11 標準のパラグラフ 5/5 および 5/6 で指定されています。

5 式が最初に「T への参照」型 (8.3.2、8.5.3) を持っている場合、その型はTその後の分析の前に調整されます。式は参照によって示されるオブジェクトまたは関数を指定し、式は式に応じてlvalueまたはxvalueです。

6 prvalueが最初に型「cv T」を持っている場合、ここでTは cv 修飾されていない非クラス、非配列型であり、Tさらなる分析の前に式の型が調整されます。

表現については以上です。何をしdecltypeますか?さて、decltype(e)与えられた式の結果を決定するルールeは、パラグラフ 7.1.6.2/4 で指定されています。

によって示される型はdecltype(e)、次のように定義されます。

eが括弧で囲まれていないid-expressionまたは括弧で囲まれていないクラス メンバー アクセス (5.2.5) でdecltype(e) ある場合、 によって名前が付けられたエンティティの型ですe。そのようなエンティティがない場合、またはeオーバーロードされた関数のセットに名前が付けられている場合、プログラムは不適切な形式です。

— それ以外の場合、exvalueの場合、はdecltype(e)の型です。T&&Te

— それ以外の場合、e左辺値decltype(e)あるT&場合、Tは の型ですe

— それ以外の場合decltype(e)は、 の型ですe

decltype指定子のオペランドは未評価のオペランドです (第 5 節)。

これは確かに紛らわしいかもしれません。部分的に分析してみましょう。初めに:

eが括弧で囲まれていないid-expressionまたは括弧で囲まれていないクラス メンバー アクセス (5.2.5) でdecltype(e) ある場合、 によって名前が付けられたエンティティの型ですe。そのようなエンティティがない場合、またはeオーバーロードされた関数のセットに名前が付けられている場合、プログラムは不適切な形式です。

これは簡単です。eが変数の名前であり、括弧で囲まれていない場合、 の結果はそのdecltype変数の型になります。そう

bool b; // decltype(b) = bool
int x; // decltype(x) = int
int& y = x; // decltype(y) = int&
int const& z = y; // decltype(z) = int const&
int const t = 42; // decltype(t) = int const

decltype(e)here の結果は、評価された式の型と必ずしも同じではないことに注意してくださいe。たとえば、式の評価はtype 、notzの値を生成します(前に見たように、段落 5/5 によってが取り除かれるため)。int constint const&&

式が単なる識別子ではない場合に何が起こるか見てみましょう:

— それ以外の場合、exvalueの場合、はdecltype(e)の型です。T&&Te

これは複雑になってきています。xvalueとは何ですか? 基本的に、これは式が属することができる 3 つのカテゴリ ( xvaluelvalue、またはprvalue ) の 1 つです。xvalueは通常、右辺値参照型である戻り値の型を持つ関数を呼び出すとき、または右辺値参照型への静的キャストの結果として取得されます。典型的な例は への呼び出しです。std::move()

規格の文言を使用するには:

[ 注: 次の場合、式はxvalueです。

— 暗黙的または明示的に、戻り値の型がオブジェクト型への右辺値参照である関数を呼び出した結果、

—オブジェクト型への右辺値参照へのキャスト、

— オブジェクト式がxvalueである非参照型の非静的データ メンバーを指定するクラス メンバー アクセス式、または

.*最初のオペランドがxvalueで、2 番目のオペランドがデータ メンバーへのポインターであるメンバーへのポインター式。

一般に、このルールの効果は、名前付きの右辺値参照が左辺値として扱われ、オブジェクトへの名前のない右辺値参照がxvalues として扱われることです。関数への右辺値参照は、名前が付けられているかどうかにかかわらず、左辺値として扱われます。—終わりのメモ]

たとえば、式std::move(x)static_cast<int&&>(x)、および(タイプstd::move(p).firstのオブジェクトの場合) は xvalues です。xvalue式に適用すると、式の型に追加されます。ppairdecltypedecltype&&

int x; // decltype(std::move(x)) = int&&
       // decltype(static_cast<int&&>(x)) = int&&

続けましょう:

— それ以外の場合、e左辺値decltype(e)あるT&場合、Tは の型ですe

左辺値とは何ですか? 非公式に言えば、左辺値式は、プログラムで繰り返し参照できるオブジェクトを表す式です。たとえば、名前を持つ変数やアドレスを取得できるオブジェクトなどです。

左辺値式であるe型の式の場合、yields . たとえば、次のようになります。Tdecltype(e)T&

int x; // decltype(x) = int (as we have seen)
       // decltype((x)) = int& - here the expression is parenthesized, so the
       // first bullet does not apply and decltype appends & to the type of
       // the expression (x), which is int

戻り値の型が である関数の関数呼び出しT&左辺値式であるため、次のようになります。

int& foo() { return x; } //  decltype(foo()) = int& 

ついに:

— それ以外の場合decltype(e)は、 の型ですe

式がxvalueでも左辺値でもない場合 (つまり、それがprvalueの場合)、 の結果decltype(e)は単純に の型になりeます。名前のない一時およびリテラルはprvaluesです。たとえば、次のようになります。

int foo() { return x; } // Function calls for functions that do not return
                        // a reference type are prvalue expressions

// decltype(foo()) = int
// decltype(42) = int

上記をあなたの質問の例に当てはめてみましょう。これらの宣言を考えると:

int i = 3, *ptr = &i, &ref = i;
decltype(ref + 0) j;
decltype(*ptr) k;
decltype(a = b) l;

は 型のprvalueを返すため、の型はjになります。単項は左辺値を生成するため、の型はになります(段落 5.3.1/1 を参照)。の結果は左辺値であるため、の型もis です(段落 5.17/1 を参照)。intoperator +intkint&operator *lint&operator =

あなたの質問のこの部分に関して:

しかし、2 番目の規則に従うと、式は代入の左側に立つことができるオブジェクトの型 (この場合は int) を生成するため、decltype は int(int&) 型への参照を生成するべきではありませんか?

あなたはおそらく本のその一節を誤解した. タイプのすべてのオブジェクトがint割り当ての左側にあるわけではありません。たとえば、以下の代入は不正です。

int foo() { return 42; }

foo() = 24; // ERROR! foo() is a prvalue expression, cannot be on the left
            // side of an assignment

代入の左側に式を表示できるかどうか (ここでは、基本データ型の組み込み代入演算子について話していることに注意してください) は、その式の値のカテゴリ( lvaluexvalue、またはprvalue )によって異なります。であり、式の値カテゴリはその型から独立しています。

于 2013-06-21T18:46:36.733 に答える
3

式の場合、例のように、引数が左辺値の場合、decltype は参照型を提供します。

7.1.6.2p4:

The type denoted by decltype(e) is defined as follows:
  — if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e)     is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions,     the program is ill-formed;
  — otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
  — otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
  — otherwise, decltype(e) is the type of e.
The operand of the decltype specifier is an unevaluated operand (Clause 5).
[ Example:
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = i; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4 = x3; // type is const double&
—end example ]
于 2013-06-21T18:21:55.500 に答える