Hellow! 你们好啊!,今天博主来介绍来介绍C++中的重头戏万能模板和完美转发还有模板参数包,这个可谓是让初学者最头疼的地方了。


目录

1 左值右值类型分类

2 引⽤折叠

重点--模板函数不显示指定模板参数的类型的自动推导的引用折叠

3 完美转发


1 左值右值类型分类

• C++11以后,进⼀步对类型进⾏了划分,右值被划分纯右值(pure value,简称prvalue)和将亡值 (expiring value,简称xvalue)。

• 纯右值是指那些字⾯值常量或求值结果相当于字⾯值或是⼀个不具名的临时对象。如: 42、true、nullptr 或者类似 str.substr(1, 2)、str1 + str2 传值返回函数调⽤,或者整形 a、b,a++,a+b 等。纯右值和将亡C++11中提出的,C++11中的纯右值概念划分等价于C++98中的右值。就是没有被右值引用的临时对象或者字面值

• 将亡值是指返回右值引⽤的函数的调⽤表达式和转换为右值引⽤的转换函数的调⽤表达,如 move(x)、static_cast<X&&>(x)

• 泛左值(generalized value,简称glvalue),泛左值包含将亡值和左值。

• 值类别 https://zh.cppreference.com/w/cpp/language/value_category和 https://en.cppreference.com/w/cpp/language/value_category这两个关于值类型的中⽂和英⽂的官⽅⽂档,有兴趣可以了解细节。

2 引⽤折叠

• C++中不能直接定义引⽤的引⽤如 int& && r = i; ,这样写会直接报错,通过模板或typedef中的类型操作可以构成引⽤的引⽤。

• 通过模板或 typedef中的类型操作可以构成引⽤的引⽤时,这时C++11给出了⼀个引⽤折叠的规则:右值引⽤的右值引⽤折叠成右值引⽤,所有其他组合均折叠成左值引⽤。

• 下⾯的程序中很好的展⽰了模板和typedef时构成引⽤的引⽤时的引⽤折叠规则,⼤家需要⼀个⼀个仔细理解⼀下。

• 像f2这样的函数模板中,T&&x参数看起来是右值引⽤参数,但是由于引⽤折叠的规则,他传递左 值时就是左值引⽤,传递右值时就是右值引⽤,有些地⽅也把这种函数模板的参数叫做万能引⽤。 • Function(T&&t)函数模板程序中,假设实参是int右值,模板参数T的推导int,实参是int左值,模 板参数T的推导int&,再结合引⽤折叠规则,就实现了实参是左值,实例化出左值引⽤版本形参的 Function,实参是右值,实例化出右值引⽤版本形参的Function。

//引用折叠
//不能直接定义引用的引用如 int& && r = i; ,这样写会直接报错,通过模板或typedef
//中的类型操作可以构成引⽤的引⽤。
//右值引⽤的右值引⽤折叠成右值引⽤,所有其他组合均折叠成左值引⽤。

// 由于引⽤折叠限定,f1实例化以后总是⼀个左值引⽤
//template<class T>
//void f1(T& x) {
//
//}
////只有T为右值引用类型时,x才为右值引用,其他情况为左值引用
////所以该模板为万能模板
//template<class T>
//void f2(T&& x) {
//
//}

//int main()
//{
//	typedef int& lref;
//	typedef int&& rref;
//	int n = 0;
//	lref& r1 = n; // r1 的类型是 int&	 &||&=&  //左值引用去引用左值引用变左值引用
//	lref&& r2 = n; // r2 的类型是 int&	 &||&&=& //左值引用去引用右值引用变左值引用
//	rref& r3 = n; // r3 的类型是 int&	&&||& =& //右值引用去引用左值引用变左值引用
//	rref&& r4 = 1; // r4 的类型是 int&&	&&||&&=&& //右值引用去引用右值引用变右值引用
//	// 没有折叠->实例化为void f1(int& x)
//	f1<int>(n);
//	//f1<int>(0); // 报错,0推导为int类型,0为右值
//	// 折叠->实例化为void f1(int& x)
//	f1<int&>(n);
//	//f1<int&>(0); // 报错
//	// 折叠->实例化为void f1(int& x)
//	f1<int&&>(n);
//	//f1<int&&>(0); // 报错,右值引用碰左值为左值引用
//	// 折叠->实例化为void f1(const int& x)
//	f1<const int&>(n);
//	f1<const int&>(0);
//	// 折叠->实例化为void f1(const int& x)
//	f1<const int&&>(n);
//	f1<const int&&>(0);
//	// 没有折叠->实例化为void f2(int&& x)
//	//f2<int>(n); // 报错
//	f2<int>(0);
//	// 折叠->实例化为void f2(int& x)
//	f2<int&>(n);//左值引用碰右值引用变左值引用
//	//f2<int&>(0); // 报错
//	// 折叠->实例化为void f2(int&& x)
//	//f2<int&&>(n); // 报错
//	f2<int&&>(0);
//	return 0;
//}

重点--模板函数不显示指定模板参数的类型的自动推导的引用折叠

由于模板函数中给定的参数的类型是 T&&(万能模板参数) 所以最后传入的实参的类型加引用折叠推导出的类型要么是 T& 左值引用类型和 T&& 右值引用类型

template<class T>
void Function(T&& t)//不显示实例化 
{
	int a = 0;
	T x = a;
	//x++;
	cout << &a << endl;
	cout << &x << endl << endl;
}
//*******************重点
//int main()
//{
//	// 10是右值,推导出T为int,模板实例化为void Function(int&& t)
//	Function(10); // 右值
//	int a;
//	// a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t)
//	Function(a); // 左值
//	// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
//	Function(std::move(a)); // 右值
//	const int b = 8;
//	// b是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int&t)
//	// 所以Function内部会编译报错,x不能++
//	Function(b); // const 左值
//	    
//	// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&&t)
//		// 所以Function内部会编译报错,x不能++
//	Function(std::move(b)); // const 右值
//	return 0;
//}

3 完美转发

• Function(T&&t)函数模板程序中,传左值实例化以后是左值引⽤的Function函数,传右值实例化以后是右值引⽤的Function函数。

• 但是结合我们前面临时变量认识的讲解,变量表达式都是左值属性,也就意味着⼀个右值(大部分存储在寄存器中,不能取地址)被右值引⽤绑定后(即创建了一个临时对象(可以取地址)存储了这个右值),右值引⽤变量表达式的属性是左值(可以对这个右值引用的别名取地址),也就是说Function函数中 t的属性是左值,那么我们把t传递给下⼀层函数Fun,那么匹配的都是左值引⽤版本的Fun函数。这⾥我们想要保持t对象的属性,就需要使⽤完美转发实现。

完美转发forward本质是⼀个函数模板,他主要还是通过引⽤折叠的⽅式实现,下⾯⽰例中传递给Function的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引⽤返回;传递给Function的实参是左值,T被推导为int&,引⽤折叠为左值引⽤,forward内部t被强转为左值引⽤返回。

//完美转发
//变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定
//后,右值引⽤变量表达式的属性是左值,也就是说Function函数中t的属性是左值,那么我们把t传
//递给下⼀层函数Fun,那么匹配的都是左值引⽤版本的Fun函数。这⾥我们想要保持t对象的属性,
//就需要使⽤完美转发实现。
template <class _Ty>
_Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept
{ // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}

void Fun(int& x) { cout << "左值引⽤" << endl; }
void Fun(const int& x) { cout << "const 左值引⽤" << endl; }
void Fun(int&& x) { cout << "右值引⽤" << endl; }
void Fun(const int&& x) { cout << "const 右值引⽤" << endl; }
template<class T>
void Function1(T&& t)
{
	//Fun(t);//t引用绑定后属性变为左值属性
	//T是由实参推导出来的类型
	Fun(forward<T>(t));//使用forward类模板函数可以保持t绑定的对象的属性
}
//int main()
//{
//	// 10是右值,推导出T为int,模板实例化为void Function(int&& t)
//	Function1(10); // 右值
//	int a;
//	// a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t)
//	Function1(a); // 左值
//	// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
//	Function1(std::move(a)); // 右值
//	const int b = 8;
//	// a是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int&t)
//	Function1(b); // const 左值
//	// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&&t)
//	Function1(std::move(b)); // const 右值
//	return 0;
//}

本期收录于博主的专栏——C++专栏,后面还会继续补充模板参数包及C语言可变参数实习原理,这里介绍的内容足够好好消化一下了。

感谢每一个观看本篇文章的朋友,更多精彩敬请期待:小董 不懂  *( ̄▽ ̄)°★*

更多推荐