C++ で関数引数の評価順序を保証したかった
[関数引数の評価順序]
C++では関数を呼び出す際に渡される引数の評価順序は未規定になっています。
どういうことかというと次のようなコードの場合、処理系によって出力される値が異なる可能性があります。
template<typename T> auto print(T value){ std::cout << value; return value; } template<typename... Args> void func(Args&&...){} // 123 と出力されるかもしれないし 321 と出力されるかもしれない func(print(1), print(2), print(3));
上記のようなコードの場合、 clang では 123
と出力されますが、gcc では 321
と出力されます。
ただし、これは関数呼び出しの場合のみで、コンストラクタの {}
呼び出しや、初期化リストの {}
では順序が『左から右の順で評価される』ことが保証されています。
ですので次のような初期化リストを呼び出した場合は期待通りの動作を行います。
template<typename T> auto print(T value){ std::cout << value; return value; } // 123 と出力されることが保証されている auto dummy = { print(1), print(2), print(3) };
[可変長テンプレート引数で引数分関数を呼び出したい]
上記の仕様を踏まえた上で、次のような処理を考えてみます。
template<typename F, typename... Args> auto call(F f, Args... args){ // 可変長引数の数だけ f を呼び出してその結果を tuple で返す return std::make_tuple(f(args)...); } using namespace std::literals::string_literals; auto twice = [](auto a){ return a + a; }; auto result = call(twice, 1, 3.5, "homu"s); assert(result == std::make_tuple(2, 7.0, "homuhomu"));
『可変長引数の数だけ f
を呼び出してその結果を tuple
として返す』という簡単な関数です。
しかし、残念なことに std::make_tuple(f(args)...)
という関数呼び出しでは、評価順序が処理系によって異なってしまいます。
auto print = [](auto a){ std::cout << a << std::endl; return a; }; // print の呼び出しが不規定なのでどういう出力になるのかが保証されてない call(print, 1, 3.5, "homu"s);
[std::make_tuple
ではなくて std::tuple
コンストラクタを使う]
上記の問題は std::make_tuple
ではなくて std::tuple
のコンストラクタを呼び出すことで回避できます。
template<typename F, typename... Args> auto call(F f, Args... args){ // std::tuple のコンストラクタを呼び出すことで評価順序を保証する return std::tuple<decltype(f(args))...>{f(args)...}; } auto print = [](auto a){ std::cout << a << std::endl; return a; }; // 出力結果が保証される call(print, 1, 3.5, "homu"s);
これで意図した順番で評価される事が保証されます。