cv修飾された仮引数はUniversal Referenceになれない
以下のコードは合法である。
#include <type_traits> #include <iostream> #include <boost/type_index.hpp> using namespace std; using boost::typeindex::type_id_with_cvr; template< typename Arg > void f( Arg&& arg ) { cout << "`Arg` is `" << type_id_with_cvr< Arg >() << "`" << endl; cout << "`Arg&&` is `" << type_id_with_cvr< decltype( arg ) >() << "`" << endl; } int main() { int a; f( a ); cout << "================" << endl; f( move( a ) ); }
出力
Start `Arg` is `int&` `Arg&&` is `int&` ================ `Arg` is `int` `Arg&&` is `int&&` 0 Finish
一方で、関数 f
の引数にcv修飾を行うとコンパイルエラーとなる。
... template< typename Arg > void f( Arg const&& arg ) ...
出力
Start prog.cc:18:5: error: no matching function for call to 'f' f( a ); ^ prog.cc:9:31: note: candidate function [with Arg = int] not viable: no known conversion from 'int' to 'const int &&' for 1st argument template< typename Arg > void f( Arg const&& arg ) ^ 1 error generated. 1 Finish
型推論と実際の型
仮引数の宣言 | 実引数 | Arg の型 |
arg の型 |
---|---|---|---|
Arg&& arg |
a |
int& |
int& |
Arg&& arg |
std::move( a ) |
int |
int&& |
Arg const&& arg |
a |
int |
int const&& |
Arg const&& arg |
std::move( a ) |
int |
int const&& |
表を見れば明白な様に、Reference Collapsingは直接的原因ではない。
コンパイルエラーの原因は、仮引数の型が int const&&
の関数に対し、渡された実引数が lvalue
であるためであり、
更にその原因は、テンプレート仮引数の型推論において、仮引数の宣言が Arg const&& arg
の時、
Arg
の型が int
に推論されているためである。
直感的には、 Arg
が int&
に推論され、 arg
に対して lvalue
が束縛されて欲しい所だが、
表を見れば、そもそも仮引数の宣言が Arg&& arg
かつ実引数が a
の時、
仮引数の宣言が参照であるにも関わらず、 Arg
の型が int&
に推論されている状況が特殊である事が分かる。
ここで規格先生にご登場願う。 以下は項 14.8.2.1 より引用。
3If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction. If P is an rvalue reference to a cvunqualified template parameter and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction. [ Example:template <class T> int f(T&&); template <class T> int g(const T&&); int i; int n1 = f(i); // calls f<int&>(int&) int n2 = f(0); // calls f<int>(int&&) int n3 = g(i); // error: would call g<int>(const int&&), which // would bind an rvalue reference to an lvalue— end example ]
以下翻訳
3仮引数がcv修飾された型の場合、最初の仮引数の型へのcv修飾子は型推論の段階で無視される。もし仮引数が参照型の場合、仮引数における参照部分は型推論に使われる。もし仮引数がcv修飾されていないrvalue reference
であり、実引数がlvalue
の場合、型推論には型「実引数へのlvalue reference
」が実引数の代わりに使われる。つまり、cv修飾されてない
Arg&& arg
が特別扱いされるのであり、仮引数がcv修飾されている場合、通常の型推論の規則、即ち 「関数テンプレートのテンプレート型引数の推論において、推論される型引数は参照を除去した型になる」という規則が適用される。