2

在类似的情况下,是否有一种将函数的​​参数转发给函数的好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)...);
}
4

2 回答 2

6

std::forward不会移动东西——它会创建一个引用,上面写着“可以从我身边移动”。实际移动发生在内部g,而不是在f哪里std::forward或被std::move调用。

的问题move只是这里的问题之一。还有在两个点将同一个对象作为引用传递两次的问题,这通常被认为是相当粗鲁的!

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

所以答案是“不要那样做”,因为它通常不安全。您必须了解两者的语义gf弄清楚正确的做法是什么,简单的转发类型接口不会反映所需的知识深度。

如果你确实对什么gf应该做什么有深刻的语义理解,那么情况就会改变。

于 2013-07-16T20:40:44.320 回答
2

您通常可以使用强制命令

  • 使用单独的语句(显然)
  • 用逗号分隔的表达式。(注意超载operator,
  • 大括号初始化的使用是有效的,因为大括号初始化列表中参数的求值顺序是它们出现的顺序1。以下具有明确定义的评估顺序:

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

但它仍然没用,因为g(...) 可能仍然会从同一个参考中移动两次。你真正想要的 rvalue refs不是

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

那么我们如何准确地实现这一点 但一般地呢?

输入指数?!

假设您有g您所描述的可变参数:

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

现在,您可以复制传递给fusing的参数

  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)...);
    }
    

接下来所需要做的就是将它们捆绑在一起:

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 constructor when using std::make_tuple

于 2013-07-16T21:01:02.893 回答