これらの概念を理解することは、形式化せずに容易ではありません。この入門書はおそらくあなたを混乱させたくないので、「lvalue」、 「 rvalue 」 、「xvalue」などの用語の導入を避けています。残念ながら、これらは がどのように機能するかを理解するための基本的なdecltype
ものです。
まず第一に、評価された式の型は決して参照型ではなく、const
非クラス型の最上位修飾型 ( int const
or など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
オーバーロードされた関数のセットに名前が付けられている場合、プログラムは不適切な形式です。
— それ以外の場合、e
がxvalueの場合、はdecltype(e)
の型です。T&&
T
e
— それ以外の場合、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 const
int const&
&
式が単なる識別子ではない場合に何が起こるか見てみましょう:
— それ以外の場合、e
がxvalueの場合、はdecltype(e)
の型です。T&&
T
e
これは複雑になってきています。xvalueとは何ですか? 基本的に、これは式が属することができる 3 つのカテゴリ ( xvalue、lvalue、またはprvalue ) の 1 つです。xvalueは通常、右辺値参照型である戻り値の型を持つ関数を呼び出すとき、または右辺値参照型への静的キャストの結果として取得されます。典型的な例は への呼び出しです。std::move()
規格の文言を使用するには:
[ 注: 次の場合、式はxvalueです。
— 暗黙的または明示的に、戻り値の型がオブジェクト型への右辺値参照である関数を呼び出した結果、
—オブジェクト型への右辺値参照へのキャスト、
— オブジェクト式がxvalueである非参照型の非静的データ メンバーを指定するクラス メンバー アクセス式、または
—.*
最初のオペランドがxvalueで、2 番目のオペランドがデータ メンバーへのポインターであるメンバーへのポインター式。
一般に、このルールの効果は、名前付きの右辺値参照が左辺値として扱われ、オブジェクトへの名前のない右辺値参照がxvalues
として扱われることです。関数への右辺値参照は、名前が付けられているかどうかにかかわらず、左辺値として扱われます。—終わりのメモ]
たとえば、式std::move(x)
、static_cast<int&&>(x)
、および(タイプstd::move(p).first
のオブジェクトの場合) は xvalues です。xvalue式に適用すると、式の型に追加されます。p
pair
decltype
decltype
&&
int x; // decltype(std::move(x)) = int&&
// decltype(static_cast<int&&>(x)) = int&&
続けましょう:
— それ以外の場合、e
が左辺値でdecltype(e)
あるT&
場合、T
は の型ですe
。
左辺値とは何ですか? 非公式に言えば、左辺値式は、プログラムで繰り返し参照できるオブジェクトを表す式です。たとえば、名前を持つ変数やアドレスを取得できるオブジェクトなどです。
左辺値式であるe
型の式の場合、yields . たとえば、次のようになります。T
decltype(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 を参照)。int
operator +
int
k
int&
operator *
l
int&
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
代入の左側に式を表示できるかどうか (ここでは、基本データ型の組み込み代入演算子について話していることに注意してください) は、その式の値のカテゴリ( lvalue、xvalue、またはprvalue )によって異なります。であり、式の値カテゴリはその型から独立しています。