C++模板编程——完美转发与可变参函数模板

1 基础概念

首先介绍几个概念:

假设现在有A、B、C三个函数。

  • 直接调用:在A函数中调用C就叫做直接调用,不拐弯抹角。
  • 转发:在A函数中调用B函数,在B函数调用C函数,这就叫做转发。这种情况下,B函数就被当作了一个跳板函数。在转发的过程中,我们可能会向B函数中传递一些参数,这些参数最终是要被传递给C函数的。这些参数经过B函数中转,间接传递给了C函数。
  • 完美转发:在转发的过程中,这些参数的类型信息可能会丢失一部分(比如实参的const信息、左值和右值属性),这种丢失参数信息的转发是不完美的。如果实参的类型信息可以通过B函数原封不动地转发给C函数,这种转发就叫做完美转发。

2 万能引用的缺点

template
void func(T&& t)
{
    cout << "t = " << TypeGetter::name << endl;
}

对于上面这段代码,我们都知道T&&的类型是万能引用,func函数模板可以接收左值也可以接收右值,t可以绑定到左值也可以绑定到右值上。

在main函数中进行如下调用:

int main()
{
    int i = 0;
    func(i);
    func(10);
}

运行结果如下:

C++模板编程——完美转发与可变参函数模板_第1张图片

从运行结果可以看到,t的类型被正确推导了。当传入左值时,t被推导为左值引用;传入右值时t被推导为右值引用。

但是,不管t的类型时左值引用还是右值引用,t本身是一个左值。也就是说,传递的实参类型信息丢失了一些,不论传递进来的实参是左值还是右值,形参的类型都变成了左值。

3 通过std::forward恢复实参的类型信息

由上文可知,形参t的类型可能是右值引用或者左值引用,但是形参t本身是一个左值。

std::forward就是为了解决这一问题。

当形参t为左值引用时,std::forward转换回它原来的左值或右值性,经过forward变换后形参t还是左值。

当形参t为右值引用时,std::forward转换回它原来的左值或右值性,经过forward变换后形参t转换成了右值。

具体用法如下:

template
void func(T&& t)
{
	std::forward(t);
}

forward的返回值就恢复了t原本的右值和左值性。

4 完美转发

template
decltype(auto) func(F&& f, T&&... t)
{
	return forward(f)(forward(t)...);
}

这就是一个完美的跳板函数,传递给func函数的所有参数都通过forward函数恢复原来的左值和右值性,传递给f的参数类型信息没有任何丢失。

5 完美转发不完美

 

template
decltype(auto) func(F&& f, T&&... t)
{
	return forward(f)(forward(t)...);
}

void temp(char*)
{
}


int main()
{
	temp(NULL); // 可以正确运行
	func(temp, NULL); // 无法通过编译
}

NULL是一个宏定义,#define NULL 0,当调用func(temp, NULL)时,经过预处理的替换就变成了func(temp, 0);形参t就被推导为int&&,它无法转化为char*类型,所以就会编译失败。

解决办法就是调用时使用nullptr而不是NULL。

func(temp, nullptr);

这样就能解决该问题。 

你可能感兴趣的:(c++,开发语言)