通过输入参数多次传递完美转发

Tem*_*Rex 4 c++ rvalue-reference move-semantics perfect-forwarding c++11

考虑以下函数accept,该函数采用类型的"通用引用" T并将其转发给具有parse<T>()lvalues重载和rvalues重载的函数对象:

template<class T>
void accept(T&& arg)
{
    parse<T>()(std::forward<T>(arg), 0); // copy or move, depending on rvaluedness of arg
}

template<class T>
class parse
{
    // parse will modify a local copy or move of its input parameter
    void operator()(T const& arg, int n) const { /* optimized for lvalues */ }
    void operator()(T&& arg)    , int n) const { /* optimized for rvalues */ }
};
Run Code Online (Sandbox Code Playgroud)

由于完美转发使源对象处于有效但未定义的状态,因此无法在同一范围内再次完美转发.下面我尝试在一个假设的split()函数中尽可能少地复制一个int代表必须对输入数据进行的数字传递:

template<class T>
void split(T&& arg, int n)
{
    for (auto i = 0; i < n - 1; ++i)
        parse<T>()(arg , i);                 // copy n-1 times
    parse<T>()(std::forward<T>(arg), n - 1); // possibly move the n-th time
}
Run Code Online (Sandbox Code Playgroud)

问题:这是在同一数据上应用多次传递的完美转发的推荐方法吗?如果没有,最小化副本数量的更惯用的方法是什么?

How*_*ant 8

问题:这是在同一数据上应用多次传递的完美转发的推荐方法吗?

是的,当您需要多次传递数据时,这是应用完美转发(或移动)的推荐方法.只有(可能)在您上次访问时从中移动.事实上,这种情况下预见在最初的招纸,而且是非常的原因,与型右值引用声明"评选的"变量没有隐含地移动.从N1377:

尽管命名的右值引用可以绑定到右值,但在使用时它们被视为左值.例如:

struct A {};

void h(const A&);
void h(A&&);

void g(const A&);
void g(A&&);

void f(A&& a)
{
    g(a);  // calls g(const A&)
    h(a);  // calls h(const A&)
}
Run Code Online (Sandbox Code Playgroud)

虽然rvalue可以绑定到f()的"a"参数,但一旦绑定,a现在被视为左值.特别是,对重载函数g()和h()的调用解析为const A&(lvalue)重载.将"a"视为f中的右值将导致容易出错的代码:首先调用g()的"移动版本",这可能会盗窃"a",然后被盗的"a"将被发送到移动h()的重载.

如果你想h(a)在上面的例子中移动,你必须明确地这样做:

    h(std::move(a));  // calls h(A&&);
Run Code Online (Sandbox Code Playgroud)

正如Casey在评论中指出的那样,传递左值时会出现重载问题:

#include  <utility>
#include  <type_traits>

template<class T>
class parse
{
    static_assert(!std::is_lvalue_reference<T>::value,
                               "parse: T can not be an lvalue-reference type");
public:
    // parse will modify a local copy or move of its input parameter
    void operator()(T const& arg, int n) const { /* optimized for lvalues */ }
    void operator()(T&& arg     , int n) const { /* optimized for rvalues */ }
};

template<class T>
void split(T&& arg, int n)
{
    typedef typename std::decay<T>::type Td;
    for (auto i = 0; i < n - 1; ++i)
        parse<Td>()(arg , i);                 // copy n-1 times
    parse<Td>()(std::forward<T>(arg), n - 1); // possibly move the n-th time
}
Run Code Online (Sandbox Code Playgroud)

上面我修正了它,正如Casey建议的那样,通过parse<T>仅使用非引用类型进行实例化std::decay.我还添加了一个static_assert,以确保客户端不会意外地犯这个错误.这static_assert不是绝对必要的,因为无论如何都会遇到编译时错误.但是,它static_assert可以提供更可读的错误消息.

这不是解决问题的唯一方法.允许客户端parse使用左值引用类型实例化的另一种方法是部分特化解析:

template<class T>
class parse<T&>
{
public:
    // parse will modify a local copy or move of its input parameter
    void operator()(T const& arg, int n) const { /* optimized for lvalues */ }
};
Run Code Online (Sandbox Code Playgroud)

现在客户不需要decay跳舞:

template<class T>
void split(T&& arg, int n)
{
    for (auto i = 0; i < n - 1; ++i)
        parse<T>()(arg , i);                 // copy n-1 times
    parse<T>()(std::forward<T>(arg), n - 1); // possibly move the n-th time
}
Run Code Online (Sandbox Code Playgroud)

parse<T&>如果需要,您可以应用特殊逻辑.