您通常可以使用强制命令
- 使用单独的语句(显然)
- 用逗号分隔的表达式。(注意超载
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)
{
}
现在,您可以复制传递给f
using的参数
索引技巧:
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 constructor when using std::make_tuple。