Prefect Forward 实现
涉及到模板偏特化、万能引用、引用折叠、模板类型推导
1. Move reference
利用模板偏特化,去除引用,保证返回一个不带引用的类型
template<typename T>
struct RemoveReference
{
using type=T;
RemoveReference()
{
std::cout<<"T"<<std::endl;
}
};
template<typename T>
struct RemoveReference<T&>
{
using type=T;
RemoveReference()
{
std::cout<<"T&"<<std::endl;
}
};
template<typename T>
struct RemoveReference<T&&>
{
using type=T;
RemoveReference()
{
std::cout<<"T&&"<<std::endl;
}
};
2. Universal reference
&
是左值引用,但&&
一定是右值引用吗,答案是否定的!
T&& t
出现以上声明时,如果T
是需要被推导出来的,比如模板,auto
,那么t
就不一定是右值引用了!其判断方法为引用折叠的规则
3. Reference Collapse
声明引用的引用编译器不会通过,但可以间接声明,比如模板类型T
本身可以隐含引用&,再加上函数参数里的引用&
引用折叠发生的情况有template、auto、typedef、decltype
x& & -- x&//左值引用的左值引用等价于左值引用
x& && -- x&//左值引用的右值引用等价于左值引用
x&& & -- x&//右值引用的左值引用等价于左值引用
x&& && -- x&&//右值引用的右值引用等价于右值引用
总之任一个为左值引用,最终都是左值引用,反之为右值引用。
例子:
int a=10;//a 左值
int &b=a;//b 左值引用
auto&& c=b;//然鹅,c是左值引用;
//auto被推导为int(reference-ness),而又因为b是左值,故被推导为int&,最终形式为int& &&,根据引用折叠,即c为左值引用
void fun1(int&&t){...}//t一定为右值引用
template<class T>
void fun2(T&&t){...}//T被推导为何类型是不定的,因此t不一定为右值引用
4 .Template type deduction
模板类型推导时,忽略引用类型参数的引用性(reference-ness)
给universal reference参数进行类型推导时,左值要特别对待(推导为
Type&
)传值参数的类型推导,入参的诸如所有const /volatile的特性都会忽略
模板板类型推导过程中,数组或函数做微参数时会退化成指针,除非模板函数的参数是引用类型
5. move
move的实现主要有两点:
- 接收万能引用,保证任何值都能传递
- 去除引用,然后强制转换成右值引用
template<typename T>
typename RemoveReference<T>::type && myMove(T&& val)
{
return static_cast<typename RemoveReference<T>::type&&>(val);
}
6. forward
注意以下foo
函数,如果传入的是左值,应该去调用左值引用类型的check
函数,否则应该调用右值引用类型的check
函数,这样便是完美转发
void check(int & val)
{
std::cout<<"this is lval"<<std::endl;
}
void check(int && val)
{
std::cout<<"this is rval"<<std::endl;
}
template<typename T>
void foo(T&& val)
{
//val一定是左值
std::cout<<"after forward:\n";
//虽然val一定是左值,但T保存着val原本究竟是左值还是右值的信息,利用它实现完美转发
check(myForward<T>(val));
}
...
foo(1);//rval
foo(myMove(x));//rval
foo(x);//lval
完美转发的实现依靠以下两点:
1.注意到foo
函数中的val本身一定是一个左值!它在完美转发中只起到传值的作用!
2.val
原本的类型信息:左值还是右值?它被保存在T
中,需要利用它
//左值完美转发
template<typename T>
T&& myForward(typename RemoveReference<T>::type& lval)
{
return static_cast<T&&>(lval);
}
//右值完美转发,注意到右值转发成一个左值是错误的
template<typename T>
T&& myForward(typename RemoveReference<T>::type&& rval)
{
static_assert(!std::is_lvalue_reference<T>::value,"rval forward to lval!");
return static_cast<T&&>(rval);
}
以左值的完美转发为例,传入类型为自定义类型Type
:
如果外部函数
foo
传入的是一个右值,其形参T&&
推导为Type&&
由于
T
被推导为Type
,因此调用forward<Type>
后传出右值引用如果外部函数
foo
传入的是一个左值,则推导为Type& &&
,根据引用折叠原则,其形参T&&
最终推导为Type &
由于
T
被推导成Type&
,因此调用forward<Type&>
后传出左值引用
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!