#include <iostream> #include <string> class A { public: template <typename T> void foo(T&& t) { T _t = std::forward<T>(t); } }; int main() { std::string s1 = "test"; A a; a.foo(s1); std::cout << "1:" << s1 << std::endl; a.foo(std::move(s1)); std::cout << "2:" << s1 << std::endl; a.foo("ok"); }
以上代码在Visual Studio 2013上测试通过。输出是:
1:test 2:
可以看到,模板函数A.foo只有一个声明和实现,但既可以接受左值,也可以接受右值。并且当S1被当作右值引用传入的时候,其值是确确实实被“丢弃”了。这就是所谓的精准转发(把t的类型准确地传递到使用者),以及通用引用(用一个T&&就可以表示所有的情况)。对于要写库的程序员来说,可谓是一个福音了。
然而,对于模板类,下面的写法看上去很好,但是编译会报错的:
template <typename T> class A { T _t; public: void foo(T&& t) { _t = std::forward<T>(t); } }; int main() { std::string s1 = "test"; A<std::string> a; a.foo(s1); std::cout << "1:" << s1 << std::endl; a.foo(std::move(s1)); std::cout << "2:" << s1 << std::endl; a.foo("ok"); }
编译后报错:
error C2664: “void A<std::string>::foo(T&&)”: 无法将参数 1 从“std::string”转换为“std::string&&” with [ T=std::string ] 无法将左值绑定到右值引用
这大概是因为,T的类型在A<std::string>的时候就确定了,因此编译器无法进行更多的类型推导。
那么怎么办呢?其实也不难,foo函数像下面这样写就可以了:
template <typename T> class A { T _t; public: template <typename X> void foo(X&& t) { _t = std::forward<X>(t); } };
在函数模板中用一个新类型就可以了。如果T跟X不一致,那么编译器反正会检查出来的。不用担心。
还有一个问题:有的时候我们会把foo的实现写在class的外面。那这个时候怎么办呢?
我本来想抛题目给大家去做。不过都到最后了卖关子也没什么意思,还是直说吧:
template <typename T> class A { T _t; public: template <typename X> void foo(X&& t); }; template <typename T> template <typename X> void A<T>::foo(X&& t) { _t = std::forward<X>(t); }
标红的两行,只能是这个顺序。类的模板定义在上面,函数的模板定义在下面。颠倒过来,报错。要写在一行也可以,先左后右就行。但是要想把尖括号打开强行并成一句,报错。