やりたいこと
簡単に言えば以下のようなことです。
auto t1 = std::make_tuple(4, 4.5, "hello"s);
auto t2 = std::make_tuple(3, 2.7, "world"s);
auto t3 = mapply(t1, t2);
// -> t3 == (7, 7.2, "helloworld")
適用する関数を配列に展開して動的に解決する方法もあるようですが、今回はあくまで静的に解決しようと思います。
実装
とりあえず問題を簡単にするために、2つのタプルを受けとり各要素同士を足して、タプルとして返すmplus
関数を考えます。まずmplus
のシグネチャを決定しましょう。
任意長のタプルを受け取るので、テンプレートの引数はclass... TArgs
とします。引数と戻り値の型はstd::tupleです。
つまり、
template <class... TArgs>
std::tuple<TArgs...> mplus(const std::tuple<TArgs...>& t1, const std::tuple<TArgs...>& t2) {
return /* この後実装 */;
}
ここで、全ての要素にoperator+()
を適用する関数を考える前に、タプルのI番目の要素同士を足す関数を考えます。関数名はplusとしておきましょう。
template <std::size_t I, class... TArgs>
std::tuple_element_t<I, std::tuple<TArgs...>>
plus(const std::tuple<TArgs...>& d1, const std::tuple<TArgs...>& d2) {
return std::get<I>(d1) + std::get<I>(d2);
}
この関数と可変長テンプレートパラメータのパック展開によってタプルのすべての要素に対しての足し算を行うことができます。ただし、複数のパラメータパックを受け取ることは基本的にはできない(タプルとして渡すなどは可能)ので、そこに注意する必要があります。
template <class... TArgs>
struct mapply_wrapper {
template <class T>
struct mapply_impl;
template <class T, std::size_t... I>
struct mapply_impl<std::integer_sequence<T, I...>> {
static std::tuple<TArgs...>
mapply(const std::tuple<TArgs...>& t1, const std::tuple<TArgs...>& t2) {
return std::make_tuple(plus<I, TArgs...>(d1, d2)...);
}
};
static std::tuple<TArgs...>
mapply(const std::tuple<TArgs...>& d1, const std::tuple<TArgs...>& d2) {
return mapply_impl<std::index_sequence_for<TArgs...>>::mapply(d1, d2);
}
};
複数の可変長パラメータを受け取るためと、部分特殊化を行うために二段階で構造体に包んでいます。
以上をまとめると、全体のコードは以下のようになります。
template <std::size_t I, class... TArgs>
std::tuple_element_t<I, std::tuple<TArgs...>>
plus(const std::tuple<TArgs...>& t1, const std::tuple<TArgs...>& t2) {
return std::get<I>(t1) + std::get<I>(t2);
}
template <class... TArgs>
struct mapply_wrapper {
template <class T>
struct mapply_impl;
template <class T, std::size_t... I>
struct mapply_impl<std::integer_sequence<T, I...>> {
static std::tuple<TArgs...>
mapply(const std::tuple<TArgs...>& t1, const std::tuple<TArgs...>& t2) {
return std::make_tuple(plus<I, TArgs...>(t1, t2)...);
}
};
static std::tuple<TArgs...>
mapply(const std::tuple<TArgs...>& t1, const std::tuple<TArgs...>& t2) {
return mapply_impl<std::index_sequence_for<TArgs...>>::mapply(t1, t2);
}
};
template <class... TArgs>
std::tuple<TArgs...> mplus(const std::tuple<TArgs...>& t1, const std::tuple<TArgs...>& t2) {
return mapply_wrapper<TArgs...>::mapply(t1, t2);
}
//auto t1 = std::make_tuple(1, 2.1, "hello"s);
//auto t2 = std::make_tuple(2, 3.4, "world"s);
//auto t3 = mplus(t1, t2);
// -> t3 == (3, 5.5, "helloworld")
任意の関数への拡張
ここまでで定義したmplusでは足し算しかできません。ということで、任意の2引数関数を受け取って適用する方法を考えてみましょう。
例えばstd::function
を受け取る方法はどうでしょうか。かなり考えましたがこの方法はできないようです。なぜならstd::function
を渡す方法では、渡した時点でstd::functionの持っている関数のシグネチャが一通りに定まっていなければならないからです。
そこで関数自体を渡すのではなく、適用したい関数をstaticメンバに持つクラスをテンプレートの引数として渡します。以下にそのコードを示します。
template <std::size_t I, class F, class... TArgs>
std::tuple_element_t<I, std::tuple<TArgs...>>
apply(const std::tuple<TArgs...>& t1, const std::tuple<TArgs...>& t2) {
return F::functor(std::get<I>(t1), std::get<I>(t2)); //operator+()の代わりにfunctorを適用
}
template <class F, class... TArgs>
struct mapply_wrapper {
template <class T>
struct mapply_impl;
template <class T, std::size_t... I>
struct mapply_impl<std::integer_sequence<T, I...>> {
static std::tuple<TArgs...>
mapply(const std::tuple<TArgs...>& t1, const std::tuple<TArgs...>& t2) {
return std::make_tuple(apply<I, F, TArgs...>(t1, t2)...);
}
};
static std::tuple<TArgs...>
mapply(const std::tuple<TArgs...>& t1, const std::tuple<TArgs...>& t2) {
return mapply_impl<std::index_sequence_for<TArgs...>>::mapply(t1, t2);
}
};
template <class F, class... TArgs>
std::tuple<TArgs...> mapply(const std::tuple<TArgs...>& t1, const std::tuple<TArgs...>& t2) {
return mapply_wrapper<F, TArgs...>::mapply(t1, t2);
}
//適用する任意の関数
struct func_wrapper {
template <class T>
static T functor(T t1, T t2) { return t1 + t2; }
};
//auto t1 = std::make_tuple(1, 2.1, "hello"s);
//auto t2 = std::make_tuple(2, 3.4, "world"s);
//auto t3 = mapply<func_wrapper>(t1, t2);
// -> t3 == (3, 5.5, "helloworld")
この方法では、関数の名前がfunctorと固定されてしまいますが、その他の特に制限はありません。
任意個の引数を受け取りたかった...。
任意個の引数を受け取れるようにしたかったのですが、どうやら私の技術力ではここまでが限界のようです。
誰かすごい人バトンタッチしてくれないかな...?