(略)を問い詰める会と curry
もう先週になってしまうのですが C++WG 会議の後に行われた「(略)を問い詰める会」に参加してきました。
結構な人数が参加されていたのですが、半分くらいの方としかお話できなかったんじゃないかなあ…少し残念でした。
id:rti7743 さんが途中「カリー化って何なんでしょう」ということを言われて、あーだこーだ言い合って、そこから「C++0x ならカリー化関数を書けるはず!」ということになって、その場はそれで終わりました。
ということで、実際に書いてみました。
…思っていたよりずっと大変でした。
#include <tuple> #include <type_traits> #include <functional> using namespace std; template <unsigned int N> struct apply_impl { template <typename Fun, typename ...Args, typename ...BoundArgs> static auto impl(Fun fun, tuple<Args...> t, BoundArgs... args) -> typename result_of<Fun (Args...)>::type { return apply_impl<N-1>::impl(fun, t, std::get<N-1>(t), args...); } }; template <> struct apply_impl<0> { template <typename Fun, typename ...Args> static auto impl(Fun fun, tuple<Args...> t, Args... args) -> decltype(fun(args...)) { return fun(args...); } }; template <typename Fun, typename ...Args> auto apply(Fun fun, tuple<Args...> t) -> typename result_of<Fun (Args...)>::type { return apply_impl<sizeof...(Args)>::impl(fun, t); } template <typename Ret, typename Args> struct RetType; template <typename Ret> struct RetType<Ret, tuple<>> { typedef Ret type; }; template <typename Ret, typename ArgsHead, typename ...ArgsTail> struct RetType<Ret, tuple<ArgsHead, ArgsTail...>> { typedef typename RetType<Ret, tuple<ArgsTail...>>::type Partial; typedef function<Partial (ArgsHead)> type; }; template <typename T> struct curry_impl; template <> struct curry_impl<tuple<>> { template <typename Ret, typename ...Args> static auto impl(Ret (*fun)(Args...), tuple<Args...> t) -> Ret { return apply(fun, t); } }; template <typename UnBoundArgsHead, typename ...UnBoundArgsTail> struct curry_impl<tuple<UnBoundArgsHead, UnBoundArgsTail...>> { template <typename Ret, typename ...Args, typename ...BoundArgs> static auto impl(Ret (*fun)(Args...), tuple<BoundArgs...> t) -> typename RetType<Ret, tuple<UnBoundArgsHead, UnBoundArgsTail...>>::type { return [fun, t](UnBoundArgsHead arg){ return curry_impl<tuple<UnBoundArgsTail...>>::impl(fun, tuple_cat(t, make_tuple(arg))); }; } }; template <typename Ret, typename ...Args> auto curry(Ret (*fun)(Args...)) -> typename RetType<Ret, tuple<Args...>>::type { return curry_impl<tuple<Args...>>::impl(fun, make_tuple()); }
いきなりこのコードを読むのはかなり大変なはずなので、まずは curry の擬似コードを書きます。
curry_impl(fun, bound_args_tuple, unbound_arg_types){ if empty(unbound_arg_types.empty?) return apply(fun, bound_args_tuple) else return function(head(unbound_arg_types) arg){ return curry_impl(fun, bound_args_tuple.cat(arg), tail(unbound_arg_types) } } curry(fun){ return curry_impl(fun, (), parameter_types(fun)) }
未束縛な引数(型)がなくなるまで再帰し、全て束縛したら関数を呼び出す、という形です。引数の束縛にはタプル(std::tuple)を、関数の呼び出しには apply を利用しています。
(lambda 式に function parameter pack を capture することが可能ならタプルや apply は不要なのですが、残念なことに gcc-4.5.1 では function parameter pack の capture は実装されていません。function parameter pack の capture については FCD の 5.1.2 p23 もしくは 14.5.3 p4 を参照してください。)
次は apply の擬似コードです。
apply_impl(fun, arg_types, args_tuple, ...args){ if (type(args...) == arg_types) return fun(args...) else return apply_impl(fun, arg_types, args_tuple, args..., args_tuple.get(len(args...))) } apply(fun, args_tuple){ return apply_impl(fun, parameter_types(fun), args_tuple) }
タプルには「tail」のような演算は標準では用意されていないため、タプルを減らしながら再帰する形では書くことができません。(C++0xFAQ の実装例ではあるのに…用意されてない理由を知っている人がいたら教えて下さい。)
仕方がないのでタプル内の値を function parameter pack に一つずつ追加し、全てうつし終えたら関数を呼び出します。
apply は少し前に一部界隈で話題になった std::pair の in-place construction (勝手に命名)の実装にも必要なはずで、それ以外にも役に立つ場面は多々あると思うのですが、これも「tail」同様標準では用意されていません…ませんよね?
まずは generic でない、関数ポインタ向けの apply を実装してみましょう。
#include <tuple> using namespace std; template <unsigned int N> struct apply_impl { template <typename Ret, typename ...Args, typename ...BoundArgs> static Ret impl(Ret (*fun)(Args...), tuple<Args...> t, BoundArgs... args){ return apply_impl<N-1>::impl(fun, t, get<N-1>(t), args...); } }; template <> struct apply_impl<0> { template <typename Ret, typename ...Args> static Ret impl(Ret (*fun)(Args...), tuple<Args...> t, Args... args){ return fun(args...); } }; template <typename Ret, typename ...Args> Ret apply(Ret (*fun)(Args...), tuple<Args...> t){ return apply_impl<sizeof...(Args)>::impl(fun, t); }
sizeof... は parameter pack の長さを返す演算子です。parameter pack なら template parameter pack でも function parameter pack でも、長さを知ることができます。
見たままなのであまり説明するところもないのですが…
今度は generic な apply を実装してみましょう。
#include <tuple> using namespace std; template <unsigned int N> struct apply_impl { template <typename Fun, typename ...Args, typename ...BoundArgs> static auto impl(Fun fun, tuple<Args...> t, BoundArgs... args) -> decltype( aplly_impl<N-1>::impl(fun, t, get<N-1>(t), args...) ) { return apply_impl<N-1>::impl(fun, t, get<N-1>(t), args...); } }; template <> struct apply_impl<0> { template <typename Fun, typename ...Args> static auto impl(Fun fun, tuple<Args...> t, Args... args) -> decltype(fun(args...)) { return fun(args...); } }; template <typename Fun, typename ...Args> auto apply(Fun fun, tuple<Args...> t) -> decltype(apply_impl<sizeof...(Args)>::impl(fun, t)) { return apply_impl<sizeof...(Args)>::impl(fun, t); }
上のコードは gcc-4.5.1 ではコンパイルできません。
問題点をはっきりさせるために、もっと簡単なコードを書いてみます。
void f(int i){} template <typename T> auto f(T t) -> decltype(f(t)) { return g(t); } int main(){ f(0); // f(int) f<int>(0); // f(T) -> f(int) f("const char*"); // ???? return 0; }
上記の「f("const char*")」を gcc-4.5.1 は「const char* を int に変換することはできない」としてエラーを出力します。f の型が決定できない、ではありません。
もっと簡単にします。
template <typename T> auto f(T t) -> decltype(f(t)) { }
このコードがおかしいことは明らかですが、これに対して gcc-4.5.1 は「h という識別子は見つからない」としてエラーを吐きます。
テンプレートの instantiation のタイミングを考えると、この振る舞いはおかしいと思うのですが、停止性云々関係なく、少なくとも手元の gcc-4.5.1 では、このようなコードを書くことはできないようです。
言語仕様としてどうなのかは、調べたんですがよく分かりませんでした…が、こんなテストが入ったみたいなので、そのうち書けるようになりそうです。
何にせよ gcc-4.5.1 では、再帰関数の返値型を求めるのに decltype を使うことはできません。
ということで、なんとかします。必要なのは引数に渡された関数の返値型なので、それを計算してやればいいわけです。
template <typename Fun, typename ...Args, typename ...BoundArgs> static auto impl(Fun fun, tuple<Args...> t, BoundArgs... args) -> decltype( fun(declval<Args>()...) ) };
declval は type_traits で定義される、その型の「値」が存在しないときに値を「ごまかす」ための関数テンプレートです。実際に呼び出すことはできませんが、sizeof や decltype などの評価されない式が書ける箇所で利用することができます。
が、こんな気持ち悪いコードを書かなくても、同様のことを行うための result_of テンプレートが利用できます。result_of は名前のとおりの type function です。
result_of は FCD によれば type_traits に定義されているはずなんですが、 gcc-4.5.1 では functional にあって、ちょっとした罠になっています。
#include <functional> template <typename Fun, typename ...Args, typename ...BoundArgs> static auto impl(Fun fun, tuple<Args...> t, BoundArgs... args) -> typename result_of<Fun (Args...)>::type
というわけで generic な aplly が書けました。
curry は再帰関数の返す型がそれぞれ違っていて apply のようにはいかないので、返値型を計算する type function を書いています(RetType)。
あとは特筆すべきことは特になくて、改良するとしたら generic な curry を enable_if を使って書くくらいでしょうか。書けるのかなあ…?
もっとスマートな書き方があったら教えて下さい。