一般に、次を使用して注文を強制できます
- 別々のステートメントを使用する(明らかに)
- コンマ演算子で区切られた式。(オーバーロードに注意
operator,
)
ブレース初期化子リスト内の引数の評価順序は、それらが出現する順序であるため、ブレース初期化の使用は機能します1。以下には、明確に定義された評価順序があります。
std::tuple<T..., T...> args {
std::forward<T>(x)...,
std::forward<T>(x)... }; // still not sane, but evaluation order defined
しかしg(...)
、同じ参照から 2 回移動する可能性があるため、それでも役に立ちません。右辺値参照に実際に必要なのは次のとおりです。
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 を渡すと、 (タプルに) 1 回移動され、invoker ヘルパーで (左辺値として) 2 回使用されることに注意してください。
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 で説明されています。
参照: std::make_tuple を使用するときにコンストラクターの未定義の実行順序を回避する方法。