C++万能引用和完美转发

一、万能引用

有的时候我们需要既能引用左值,又能引用右值的情况。并且不想使用常量左值引用(既能引用左值,又能引用右值),因为其具有常量性。这时,我们就需要实现一个万能引用即可。

请见如下2组例子

void func1(int &&m) {} //m为右值引用
template<typename T>
void func2(T &&n){}  //n为万能引用

int getVal() { return 8; }
int &&x = getVal(); //x为右值引用
auto &&y = getVal(); //y为万能引用,当前引用了右值

上面代码中m和x为右值引用毋庸置疑,而非常相似的n和y都是万能引用。因为万能引用都发生了类型推导,在推导过程中,如果初始化的对象是左值,则其被推导为左值引用;初始化的对象是右值,则其被推导为右值引用。

万能引用能够实现上诉的工作,是因为应用到了引用折叠。下面简单介绍下引用折叠规则

类模板型T实际类型最终类型
T&RR&
T&R&R&
T&R&&R&
T&&RR&&
T&&R&R&
T&&R&&R&&

从上表的总结来看,可以得出一个规律:在整个推导过程中,只要有左值引用参与进来,最后的推导结果就是左值引用。

请见如下代码

template<class T>
void func(T&& param)
{
    std::cout << "T type: " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;
    std::cout << "param type: " << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name()
            << std::endl;
}

int main()
{
    int a = 1;
    std::cout << "a:\n";
    func(a);

    std::cout << "6:\n";
    func(6);
}

输出结果 

a:
T type: int&
param type: int&

6:
T type: int
param type: int&&

根据上述代码,我们可以知道

1、模板函数func传入int型左值,T被推导为int&,形参param的类型为T&&,T代入后为 int& &&,根据引用折叠规则(类模板型为T&&,T实际类型为int&,R=int,最终类型为R&,即int&),param的类型为int&,和输出结果一致;

2、传入左值引用,T被推导为int&,结果和传入左值一致,param的类型最终推导为int&;

3、传入右值,T被推导为int,根据引用折叠规则(类模板型为T&&,T实际类型为int,R=int,最终类型为R&&,即int&&),param的推导结果为int&&。

所以,上诉函数模板func,无论传入的是左值还是右值,最终形参param都是以引用形式执行func函数体,并且保留了左值和右值的属性。

二、完美转发

我们刚刚了解到了万能引用,下面我们介绍下万能引用比较重要的一个用途——实现完美转发。

template<class T>
void showType(T t) { std::cout << typeid(t).name() << std::endl; }

template<class T>
void func(T t) { showType(t); }

int main() {
	std::string str = "hello c++";
	normalForwarding(str);
}

上面代码中的函数 func实现的是一个转发的功能(即把传入的参数的信息转发给showType函数),虽然使用了模板可以转发各种类型的参数,但是因为其是按值传入的方式,在转发过程中会额外发生临时对象的复制,所以我们还是需要想办法寻求传入引用的方式实现转发。

我们的目标十分明确——传入引用,实现转发函数模板。

然而,我们知道如果传入了左值,我们需要使用左值引用;如果传入右值,我们应该使用右值引用。

这时,我们很容易就能想到,我们使用万能引用既可以转发左值引用,又可以转发右值引用。万能引用用在这里简直太合适了!

所以,转发可以这样实现:

首先,重载两个showType函数,可以根据重载特性区分传入参数的左值右值属性

//接收左值属性参数
template<class T>
void showType(T& param)
{
    std::cout << "get l-value\n";
}
//接收右值属性参数
template<class T>
void showType(T&& param)
{
    std::cout << "get r-value\n";
}

转发函数我们使用上述func模板函数,这个函数是万能引用实现的

template<class T>
void func(T&& param)
{
    std::cout << "T type: " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;
    std::cout << "param type: " << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name()
            << std::endl;
    showType(static_cast<T&&>(param)); //增加转发
}

上述代码,我们使用func将传入参数转发给了showType。

输出结果如下

a:
T type: int&
param type: int&
get l-value
6:
T type: int
param type: int&&
get r-value

需要注意的是,我们在将t转发给showType的时候使用static_cast<T&&>做了一步转化。因为C++规定:具名的右值引用是左值,不具名的右值引用是右值。无论传入的是什么类型的引用,param都是具名对象,即是左值。如果不做任何处理,即showType(param); 则a和6都会输出get l-value的结果,传入参数的左值右值属性没有得到保留。

那么,static_cast<T&&>是怎么实现保留参数属性的呢?

我们之前分析过,如果传入参数是int型左值a,则T会被推导为int&,则T&&即int& &&使用引用折叠会推导为int&,即左值引用类型的实参,传入showType会得到get l-value的结果;

如果传入参数是int型右值6,则T被推导为int,则T&&就是int&&,即右值引用类型的实参,传入showType会得到get r-value的结果。

【参考&致谢】

《现代C++语言核心特性解析》---谢丙堃

引用折叠和完美转发 - 知乎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值