yohhoyの日記

技術的メモをしていきたい日記

decltype(e)とdecltype((e))のキモチ

プログラミング言語C++における decltype指定子(decltype-specifier) の振る舞いについてメモ。

int x = 42;

// 括弧なしの変数名 x
decltype( x ) y = x;  // int 型
// 括弧付きの式 (x)
decltype((x)) z = x;  // int& 型

一見すると奇妙に思えるdecltype(e)型導出ルールは、「式eの型宣言を探す」という指針に基づいている。

  • 変数名xソースコード上に直接登場する変数x宣言時の型intとなる。
  • (x):式(x)ソースコード上の宣言には現れない。式(x)の左辺値としての性質(lvalueness)を保持する参照型int&となる。*1

C++11言語仕様へのdecltype導入当時の提案文書(PDF) N2115 Decltype (revision 6): proposed wording より一部引用。*2

2.2 Semantics of decltype
Determining the type decltype(e) build on a single guiding principle: look for the declared type of the expression e. If e is a variable or formal parameter, or a function/operator invocation, the programmer can trace down the variable's, parameter's, or function's declaration, and find the type declared for the particular entity directly from the program text. This type is the result of decltype. For expressions that do not have a declaration in the program text, such as literals and calls to built-in operators, lvalueness implies a reference type.

おまけ:括弧付きの変数名のみからなる式(e)の扱いは検討当時も紆余曲折あったようで、(PDF)N1705(rev.4)時点ではdecltype((e))decltype(e)は等価とされていたが、その後のN2115(rev.6)にて現行仕様へと再変更されている。

C++17 10.1.7.2/p4より引用(下線部は強調)。*3

For an expression e, the type denoted by decltype(e) is defined as follows:

  • if e is an unparenthesized id-expression naming a structured binding (11.5), decltype(e) is the referenced type as given in the specification of the structured binding declaration;
  • otherwise, if e is an unparenthesized id-expression or an unparenthesized class member access (8.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 8).
[Example:

const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 17;   // 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] [Note: The rules for determining types involving decltype(auto) are specified in 10.1.7.4. --end note]

関連URL

*1:C++文法上は変数宣言時に括弧を記述できるが(例:int (y) = 42;)、この冗長な括弧は単に無視されてdecltypeの振る舞いには影響を与えない。https://gist.github.com/yohhoy/7b63eafcc42e078f34294857698adfd9

*2:C++仕様には後続文書(PDF)N2343採択されている。

*3:1番目のBulletは 構造化束縛(structured binding) に対するルール。構造化束縛で導入される名前の型導出は、通常の変数宣言よりも複雑な規則となっている。