完美的转发和功能参数评估的顺序

时间:2013-07-16 19:48:59

标签: c++ templates c++11 rvalue-reference

在类似

的情况下,有没有一种方法可以将函数f的参数转发给函数g
template<typename... T>
void g(T && ... x);

template<typename... T>
void f(T && ... x)
{
    g(x..., x...);
}

在下一个代码x中可以移动两次

template<typename... T>
void f(T && ... x)
{
    g(std::forward<T>(x)..., std::forward<T>(x)...);
}

在下一个代码中std::forward<T>(x)...可以在x...

之前进行评估
template<typename... T>
void f(T && ... x)
{
    g(x..., std::forward<T>(x)...);
}

2 个答案:

答案 0 :(得分:6)

std::forward不会移动东西 - 它会创建一个引用,表示“可以离开我”。实际移动发生在g,而不是f,其中std::forwardstd::move被调用。

move的问题只是其中一个问题。还有一个问题是将同一个对象作为参考传递两次,这通常被认为是非常粗鲁的!

我们可以通过在f中创建临时对象并通过引用传递它来解决这个问题,但这会导致严重的问题:引用通常用于从函数返回值,并且我们使用相同的变量两次 - 我们无法返回两个结果。

所以答案是“不要那样做”,因为它通常不安全。您必须知道gf的语义,以确定正确的操作是什么,并且简单的转发类型接口不会反映所需知识的深度。

如果您对gf应该做什么有深刻的语义理解,那么情况会发生变化。

答案 1 :(得分:2)

您通常可以使用

强制执行订单
  • 使用单独的陈述(显然)
  • 由逗号运算符分隔的表达式。 (注意过载operator,
  • 使用大括号初始化是有效的,因为大括号初始化列表中参数的求值顺序是它们出现的顺序 1 。以下是明确定义的评估顺序:

    std::tuple<T..., T...> args { 
         std::forward<T>(x)..., 
         std::forward<T>(x)... }; // still not sane, but evaluation order defined
    

但它仍然无用,因为g(...) 可能仍会从同一个引用移动两次。对于右值参考,你 实际上想要 的是 不是

g(rvalue, std::move(rvalue));            // or
g(std::move(rvalue), rvalue);            // or even
g(std::move(rvalue), std::move(rvalue)); // [sic]

唯一合理的方式是:

g(lvalue=std::move(rvalue), lvalue); // BUT: fix the evaluation order

那么我们如何实现精确 ,但一般来说呢?

输入Indices?!

假设你有如你所描述的变量g

template<typename... T>
void g(T && ... x) 
{ 
}

现在,您可以使用

复制传递给f的参数
  1. 索引技巧:

    namespace detail // indices 
    {
        template<std::size_t... Is> struct seq{};
        template<std::size_t I, std::size_t... Is>
            struct gen_seq : gen_seq<I-1, I-1, Is...>{};
        template<std::size_t... Is>
            struct gen_seq<0, Is...>{ using type = seq<Is...>; };
    }
    
  2. 调用者帮助函数

    #include <tuple>
    
    template<typename Tuple, std::size_t... Is>
    void f_invoke_helper(Tuple const& tup, detail::seq<Is...>) 
    { 
        g(std::get<Is>(tup)..., std::get<Is>(tup)...);
    }
    
  3. 接下来要求的就是将它们捆绑在一起:

    template<typename... T>
    void f(T && ... x)
    {
        f_invoke_helper(
                std::make_tuple(std::forward<T>(x)...), 
                typename detail::gen_seq<sizeof...(T)>::type());
    }
    

    请注意,如果您传递rvalue-refs,它将移动一次(进入元组)并在调用者助手中使用两次(作为左值):

    int main()
    {
        std::string x = "Hello world";
        int i = 42;
    
        // no problem:
        f(i, -42, std::move(x));
    }
    

    希望这有帮助!

      

    PS。正如已经恰当地指出的那样,只是说

    可能要容易得多
    template<typename... T>
    void f(T&&... x) { g(x..., x...); }
    
         

    除了实际上将可移动参数移动到元组中之外,我没有想到一种方法,其中元组成语不会产生相同的结果。


    1 T {...}的语义在12.6.1中描述了参见:how to avoid undefined execution order for the constructors when using std::make_tuple