きままにブログ

プログラミングを主とした私のメモ帳です。寂しいのでコメントください笑

関数の合成を保存し、実行する

関数をどんどん適用できる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は、ラムダ式型推論をさせるためのヘルパー関数です。このように左から順番に評価されていることがわかります。