関数の合成を保存し、実行する
関数をどんどん適用できるAdaptorを考えます。
背景
ある関数fがあって、それに対してさらに関数gを適用した関数h = g(f)を考えます。これをC++で表現するというのがこの記事の目的です。ところでどうしてそんなことが必要なのかというと、LINQ for C++を自分で実装しようと考えた時、まずはこのようなアダプタを作らなければならないと思ったからです。これが完成すればあとは適当なrangeに対してイテレータで回すたびに実行すればいいことになります。ただし、takeやreverseとなるとそうはいかなくなりそうなのでとりあえず今回はこのようなものを考えました。
Step1
関数を保存でき、operator()(T)で実行できるようなクラスを作りました。
template <typename Func> class adaptor { public: adaptor(Func f) : f(f) { } template <typename T> auto operator()(T t) { return f(t); } Func f; };
このクラスによって関数を(オーバーヘッドなしで)保持できます。
Step2
ではこれに対して>>adaptorにて関数の合成を行えるようにしたいと思います。
template <typename ItsFunc> mix_adaptor<adaptor<Func>, adaptor<ItsFunc>> operator>>(adaptor<ItsFunc> a) { return mix_adaptor<adaptor<Func>, adaptor<ItsFunc>>(*this, a); }
Step3
合成後もインターフェイスとしてoperator()(T)があれば後々問題が生じないため、とりあえずこういうクラスを作りました。したがって合成アダプタクラスも
template <typename Adaptor1, typename Adaptor2> class mix_adaptor { public: mix_adaptor(Adaptor1 a1, Adaptor2 a2) : a1(a1), a2(a2) { } template <typename T> auto operator()(T t) { return a2(a1(t)); } template <typename ItsFunc> mix_adaptor<mix_adaptor, adaptor<ItsFunc>> operator>>(adaptor<ItsFunc> a) { return mix_adaptor<mix_adaptor, adaptor<ItsFunc>>(*this, a); } Adaptor1 a1; Adaptor2 a2; };
と定義しました。
Step4
以上で合成して関数を実行することができます :
template <typename Func> adaptor<Func> make_adaptor(Func func) { return adaptor<Func>(func); } int main() { auto a = make_adaptor([](int n) { return n + 1; }); auto b = make_adaptor([](int n) { return n * 2; }); auto c = make_adaptor([](int n) { return n * n; }); auto d = a >> b >> c; auto e = b >> c >> a; printf("%d\n", a(1)); // 2 printf("%d\n", b(1)); // 2 printf("%d\n", c(1)); // 1 printf("%d\n", d(1)); // 16 printf("%d\n", e(1)); // 5 getchar(); return 0; }
make_adaptorは、ラムダ式の型推論をさせるためのヘルパー関数です。このように左から順番に評価されていることがわかります。