在类似
的情况下,有没有一种方法可以将函数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)...);
}
答案 0 :(得分:6)
std::forward
不会移动东西 - 它会创建一个引用,表示“可以离开我”。实际移动发生在g
内,而不是f
,其中std::forward
或std::move
被调用。
move
的问题只是其中一个问题。还有一个问题是将同一个对象作为参考传递两次,这通常被认为是非常粗鲁的!
我们可以通过在f
中创建临时对象并通过引用传递它来解决这个问题,但这会导致严重的问题:引用通常用于从函数返回值,并且我们使用相同的变量两次 - 我们无法返回两个结果。
所以答案是“不要那样做”,因为它通常不安全。您必须知道g
和f
的语义,以确定正确的操作是什么,并且简单的转发类型接口不会反映所需知识的深度。
如果您对g
和f
应该做什么有深刻的语义理解,那么情况会发生变化。
答案 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
那么我们如何实现精确 ,但一般来说呢?
假设你有如你所描述的变量g
:
template<typename... T>
void g(T && ... x)
{
}
现在,您可以使用
复制传递给f
的参数
索引技巧:
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...>; };
}
和调用者帮助函数:
#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 constructors when using std::make_tuple。