STL アルゴリズムの使用例は、次のようなリスト初期化コンテナーで示されていることがよくあります。
std::vector< int > v{1, 2, 3, 4};
しかし、このアプローチが(sとは異なり) ( s とは異なり)クラスintに使用される場合、たとえ右辺値( move to ) で渡されたとしても、 1 の過剰なコピー操作を意味std::initializer_listしますconst_iterator。
この問題を解決するために、次の (C++17) アプローチを使用します。
template< typename Container, typename ...Args >
Container make_container(Args &&... args)
{
Container c;
(c.push_back(std::forward< Args >(args)), ...);
// ((c.insert(std::cend(c), std::forward< Args >(args)), void(0)), ...); // more generic approach
return c;
}
auto u = make_container< std::vector< A > >(A{}, A{}, A{});
しかし、次のようにすると物足りなくなりました。
A a;
B b;
using P = std::pair< A, B >;
auto v = make_container< std::vector< P > >(P{a, b}, P{std::move(a), std::move(b)});
ここでは、コピー操作を移動操作に置き換えることで、値ごとに 1 つのコピー操作を節約したいと考えています (移動するAかB、コピーするよりもはるかに安価であると仮定します) が、関数の引数の評価順序がC++ では定義されていません。私の現在の解決策は次のとおりです。
template< Container >
struct make_container
{
template< typename ...Args >
make_container(Args &&... args)
{
(c.push_back(std::forward< Args >(args)), ...);
}
operator Container () && { return std::move(c); }
private :
Container c;
};
A a; B b;
using P = std::pair< A, B >;
using V = std::vector< P >;
V w = make_container< V >{P{a, b}, P{std::move(a), std::move(b)}};
コンストラクターの本体で自明でない作業を行うことは、しばしば悪い習慣と見なされますが、ここではリストの初期化の特性そのものを集中的に使用しました。
特定の観点から見ると、それは完全に間違ったアプローチですか?上記以外に、このアプローチの欠点は何ですか? 現在(C++11、C++14、C++1zで)関数引数の評価の予測可能な順序を達成するための別の手法はありますか?