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_reference
は T, T&&
を T&&
で返し、 T&
を T&
で返す。
では何故 add_rvalue_reference<T>::type
は T&
を T&
で返すかというと、
N3337 の 20.9.7.2 曰く
This rule reflects the semantics of reference collapsing (8.3.2).
とのことです(丸投げ)。