2017-03-29

对VS2013下C++11的精准转发与通用引用的一点研究

在C++11中,允许用以下方式编写模板函数:
#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);
}
标红的两行,只能是这个顺序。类的模板定义在上面,函数的模板定义在下面。颠倒过来,报错。要写在一行也可以,先左后右就行。但是要想把尖括号打开强行并成一句,报错。

没有评论:

发表评论