程序:变参模板输出多种类型参数 主要涉及右值引用,移动语义 可参考:右值引用....等等
先上代码:
#include <iostream> #include <string> #include <chrono> struct St { std::string description; }; template <class T> void s_printf(T&& value) { std::cout << value << std::endl; } void s_printf(St& v) { std::cout << v.description << std::endl; } template<typename T, typename... Args> auto s_printf(T&& value, Args&&... args) { s_printf(value); s_printf(args...); } int main() { St st {"Learn by Doing"}; auto t1 = std::chrono::high_resolution_clock::now(); st_printf(1, 2, 3.14, 5, 6, 7, 8, 9, 10, 11, 12, 13, "hello world", std::move(st)); auto t2 = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count(); std::cout << duration << std::endl; return 0; }
auto t1 t2为了测试之间所包含代码执行的时间。
上面
st_printf(1, 2, 3.14, 5, 6, 7, 8, 9, 10, 11, 12, 13, "hello world", std::move(st));
中 1 2 3.14 .... 13 这些都是字面值常量,是右值;(判断右值左值:能否对其使用取地址&,一大堆博文这么写/自己查)
而“hello world”是字符串字面值,类型为const char[] ,可以对其&取地址,所以是左值。
而&&在实参推导的时候,图说函数模板右值引用参数(T&&)类型推导规则(C++11)
不想打开连接的直接看总结,我把总结摘过来:
只要我们传递一个基本类型是A④的左值,那么,传递后,T的类型就是A&,形参在函数体中的类型就是A&。
只要我们传递一个基本类型是A的右值,那么,传递后,T的类型就是A,形参在函数体中的类型就是A&&。
另外,模板参数类型推导是保留cv限定符(cv-qualifier,const和volatile限定符的统称)的,具体例子见《完美转发和标准库forward函数》。
①这里指形参在函数体中的实际类型
②函数返回的不具名左值引用依旧是左值,例如,上面代码中,Get(3)=5;是可以的。
③具名的右值引用是左值,不具名的右值引用是右值。
④这里,“基本类型是A”意思是:A,A&,A&&及A类型的字面值的基本类型都是A
所以“hello world”即便是左值,也可以把它传递给模板中右值引用的“形参”,这句话不严谨 仅供理解。
关于std::move
我们都知道move接受任意类型,并在内部通过static_cast<...>将其转换为&&
所以以下代码是可以正确运行的:
struct St { std::string description; }; void s_printf(St&& v) {//如果换成& 则不能运行 因为传入的是右值引用 std::cout << v.description << std::endl; } int main() { st_printf(std::move(st));}
那么为什么一开始的程序是&?
我们来看
st_printf(1, 2, 3.14, 5, 6, 7, 8, 9, 10, 11, 12, 13, "hello world", std::move(st));
这是多个参数的,所以他会匹配
template<typename T, typename... Args> auto s_printf(T&& value, Args&&... args) { s_printf(value); s_printf(args...); }
这个时候,有一条规则不得不说了
右值引用和左值引用
①右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。
②左值引用是具名变量/对象的别名,右值引用是匿名变量/对象的别名。
③左值和右值是独立于它的类型的,即左右值与类型没有直接关系,它们是表达式的属性。具名的右值引用是左值,匿名的右值引用是右值。如Type&& t中t是个具名变量(最简单的表达式),t的类型是右值引用类型,但具有左值属性。而Type&& func()中的返回值(是个表达式)是右值引用类型,但具有右值属性(因为是个匿名对象)。
即:具名的右值引用是左值(左右值只表示表达式的属性,与表达式的类型没有关系)所谓匿名对象也就是临时对象,自然也就是xvalue 将亡值--右值
这个时候使用
s_printf(args...);
传到
void s_printf(St& v) { std::cout << v.description << std::endl; }
这个时候的arg是右值引用类型,但是他是左值,所以只能使用&,如果使用&& 那么就会匹配不到
当然了,移动语义在类的移动构造函数上有更大的意义。这个程序主要为了理解左值右值、左值引用右值引用。
利用重载<<来解决自定义类型的输出
std::ostream& operator<<(std::ostream& o, St &s) { o<<s.description;return o; } auto s_printf(){} template<typename T, typename... Args> auto s_printf(T &&value, Args&&... args) { std::cout<<value<<std::endl; s_printf(std::forward<Args>(args)...); }
不定义template的“特化”版本(不严谨,仅仅为了表达意思),转而重载<<,利用重载匹配来解决st类型的输出。
std::forward() 将一个或多个实参连同类型不变地转发给其他函数。它与move不同 必须通过显式模板实参来调用
forward<T>的返回类型是T&& 通过其返回类型上的引用折叠,forward可以保持给定实参的左值、右值类型。
为什么定义一个空的s_printf()
还没搞明白
难道最后forward会返回一个空值?也不对啊?