declval<T>() の返り値型が T&& である理由

メモ。

そもそも declval とは?

T1, T2 があり、それらの値を乗算した結果の型を得たい場合、 素直に考えると

decltype( T1() * T2() )

で得られそうだが、これは T1, T2 共にデフォルトコンストラクタが無いと T1(),T2()コンパイルエラーになってしまう。

そこで std::declval が使える。

decltype( std::declval<T1>() * std::declval<T2>() )

以下略

もし返り値型が T だった場合

struct Widget;

template<class T>
T my_declval();

decltype( my_declval<Widget>() * my_declval<Widget>() ) val;

T が不完全型の時にエラーになる。

もし返り値型が T& だった場合

struct Widget {
  Widget(Widget const&) = delete;
  Widget(Widget&&);  
};

template<class T>
T& my_declval();

int somefunc( Widget );

// エラー!
decltype( somefunc( my_declval<Widget>() ) ) val1; 

// OK
decltype( somefunc( std::move(my_declval<Widget>()) ) ) val1;

ムーブ構築しか出来ない場合に面倒くさくなる。

補足

尚、 declval は基本的に T&& を返すので

int somefunc( int& );
decltype( somefunc( std::declval<int>() ) ) val;

このコードがエラーになる(msvcだと通っちゃうんだけど)。 こういう場合は、

int somefunc( int& );
decltype( somefunc( std::declval<int&>() ) ) val;

と、渡す型に & を付けてやれば良い。 これが何故通るかというと、実は declval の返り値型は add_rvalue_reference<T>::type だからである。 add_rvalue_referenceT, T&&T&& で返し、 T&T& で返す。

では何故 add_rvalue_reference<T>::typeT&T& で返すかというと、 N3337 の 20.9.7.2 曰く

This rule reflects the semantics of reference collapsing (8.3.2).

とのことです(丸投げ)。