読者です 読者をやめる 読者になる 読者になる

apply()を標準アルゴリズムと組み合わせられるように改善する

C++

N3915 apply() call a function with arguments from a tuple (V3)

現在、C++14後のLibrary Fundamentals TSに提案されているapply()関数。これは、タプルを引数として関数を呼び出すというユーティリティ関数ですが、機能が小さすぎるので実用に耐えません。

この関数は、実際には複数のRangeをzipしてアルゴリズムに渡すために使用します。そのため、標準アルゴリズムと組み合わせて使えなくてはなりません。標準アルゴリズムと組み合わせられるようにするには、「複数引数を受け取る関数を、タプルを引数として実行する形に変換する」という機能とその引数適用を、2段階に分けて行える必要があります。

vector<tuple<T1, T2, T2>> v;
for_each(v.begin(), v.end(), make_apply([](T1 a, T2 b, T3 c) {
    cout << a << ' ' << b << ' ' << c << endl;
}));

これと同等の機能は、Boost.Fusionにだいぶ前からmake_fused()という関数で提供されています。標準にこの機能を導入するのであれば、Boost.Fusionの経験を反映させたものにするべきだと思います。

2段階化の実装は、apply()関数のラッパーとして実装できます。

#include <tuple>
#include <utility>

template<typename F, typename Tuple, size_t... I>
auto apply_impl(F&& f, Tuple&& args, std::index_sequence<I...>)
{
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
}

template<typename F, typename Tuple,
         typename Indices = std::make_index_sequence<std::tuple_size<Tuple>::value>>
auto apply(F&& f, Tuple&& args)
{
    return apply_impl(std::forward<F>(f), std::forward<Tuple>(args), Indices());
}

template<typename F, typename Tuple, size_t... I>
auto apply_impl(F&& f, const Tuple& args, std::index_sequence<I...>)
{
    return std::forward<F>(f)(std::get<I>(args)...);
}

template<typename F, typename Tuple,
         typename Indices = std::make_index_sequence<std::tuple_size<Tuple>::value>>
auto apply(F&& f, const Tuple& args)
{
    return apply_impl(std::forward<F>(f), args, Indices());
}

template <class F>
class apply_functor {
    F f_;
public:
    explicit apply_functor(F&& f)
        : f_(std::forward<F>(f)) {}

    template <class Tuple>
    auto operator()(Tuple&& args)
    {
        return apply(std::forward<F>(f_), std::forward<Tuple>(args));
    }

    template <class Tuple>
    auto operator()(const Tuple& args)
    {
        return apply(std::forward<F>(f_), args);
    }
};

template <class F>
apply_functor<F> make_apply(F&& f)
{
    return apply_functor<F>(std::forward<F>(f));
}

サンプル:

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

int main()
{
    std::vector<std::tuple<int, char, std::string>> v = {
        {1, 'a', "Alice"},
        {2, 'b', "Bob"},
        {3, 'c', "Carol"}
    };
    
    std::for_each(v.begin(), v.end(),
      make_apply([](int a, char b, const std::string& c) {
          std::cout << a << ' ' << b << ' ' << c << std::endl;
      }
    ));
}

出力:

1 a Alice
2 b Bob
3 c Carol

という意見を出そうかと思っています。ようやく実装コードを書く時間がとれたので、もうちょっと改善できるところがないかを検討してから出します。

参照