メタ関数の高階関数
apply<sprout::types::quote<F>, Args...>::type は F<Args...>::type を返す
— 狂える中3女子ボレロ村上/陶芸C++er (@bolero_MURAKAMI) 2014, 8月 18
apply<sprout::types::self<F>, Args...>::type は F<Args...> を返す
— 狂える中3女子ボレロ村上/陶芸C++er (@bolero_MURAKAMI) 2014, 8月 18
これらの動作は Boost.MPL ではどちらも mpl::quoteN が担っているけど、SFINAEフレンドリなメタ関数(不正な引数の場合 type メンバが定義されない)の場合挙動が混乱しやすいので、sprout では quote と self に分けることにした。
— 狂える中3女子ボレロ村上/陶芸C++er (@bolero_MURAKAMI) 2014, 8月 18
まず言葉の説明から
メタ関数
テンプレート引数として型を受け取り::typeで結果を返す型のこと
More C++ Idioms/メタ関数(Metafunction) - Wikibooks
例えばstd::add_pointer
std::add_pointer<int>::type//int*
高階関数
ハイオーダー関数
関数を受け取ったり、関数を返す関数のこと
高階関数 - Wikipedia
c++でいうとany_ofとか
で、メタ関数の高階関数とはメタ関数を受け取る高階関数のことである
例えばリストとメタ関数を受け取りリストの各要素に関数を実行して返す関数mapを愚直に実装しようとすると以下のようになる
template<class,template<class>class> struct map;//ダミーの定義 template<class...T,template<class>class F> struct map<List<T...>,F> { using type=List<typename F<T>::type...>; }
分かりやすい
ところでこの愚直なmapの実相にはテンプレートテンプレートパラメータが出てくる
テンプレートテンプレートパラメータにはいくつか問題がある
必要なタイプが多い、キモい、いろいろあるがとりあえずメタ関数で返すことができない
メタ関数のbindやカリー化はかなり有用であるがbindやカリー化は関数を返す関数である
しかしメタ関数はテンプレートテンプレートパラメータは返せない
これは結構問題だ
そこでメタ関数を普通の型に丸めるという方法が考案さえた
そして高階関数はこの丸められた型を受け取り使用するという方法を取るという方法を用いるようになった
丸められたメタ関数はこんな具合だ
struct F { template<class...T> struct apply { using type=/**/; }; };
このようにapplyというメタ関数を持つ感じになる
考え方はテンプレート関数のoperator()()をもつクラスと同じだ
struct hoge { template<class T> operator()(T x) {} }
そしてこのクラスを
typename F::apply<a,b,c>::type;
として実行する
実用としてはメタ関数(applyとか)を作って
typename apply<F,a,b,c>::type; //==F::apply<a,b,c>::type;
のように使われる
しかし上記の丸められたスタイルの関数は普通は作るのも使うのもだるい
たとえばif_
if_<a,b,c>; //いつもの目に優しいメタ関数 apply<if_,a,b,c>; //丸められたスタイル
上のほうがシンプルだ
if_を実装する側もいちいちラッパしたりapply書いたりするのはだるい
なので普段使いはいつものきれいなメタ関数を使い、高階関数を用いるときのみ丸めたメタ関数を使うことにした
そして何とかきれいなメタ関数から丸めたメタ関数に変換する機構が必要なので
このブログの冒頭で中三女子氏が言っていたquoteが作られることになった*1
この機構はたぶんこんな感じの実装だ
template<template<class...>class F> struct quote { template<class...T> struct apply :F<T...> {}; };
この機構のおかげで高階メタ関数を気軽に使うことができるようになった
map<list,bind<quote<if_>,arg<0>,void*,void>>; //List<true_type,false_type>→List<void*,void>
しかしてまだ問題がある
std::is_sameのような::value返す系メタ関数だ
一般にメタ関数というのは::typeを返す。しかしis_sameはintegral_constantを継承する
有効なtypeを持たない。ここでquoteを用いるとapplyは::typeを返す関数ではなくなり高階関数が求めるメタ関数性を失ってしまう。よってこういうメタ関数用のquoteが必要である
それが中三女史氏が言っていたselfになる
おそらくselfの実装はこんな感じだろう
template<template<class...>class F> struct self { template<class...T> struct apply { using type=F<T...>; }; };
quoteとちょっと違うがまあこんな感じだろう
これで晴れて::valueを返す型を返す関数ができた
is_XXX系メタ関数はfilter関数などで活躍するのでselfもfilterにおいて活躍するであろう
filter<list,self<std::is_pointer>>
140819 selfについて追記
中三女史のコメントを見てもらうと分かるがstd::integral_constantは自身を返す::typeを持っている
なんか長い間すごい勘違いしてたよ
integral_constant、true_type、false_type (C++11) - cpprefjp - C++ Library Reference
でintegral_constanから継承されるis_sameは::typeを持つのでselfを使う理由にはならない
いつ使うのか?というのは例えば
self<List>
のような場合らしい
そういえば俺俺ライブラリで、高階関数に渡す用にmake_Listって関数を作ってた記憶がある
またselfとquoteを一つのquoteにまとめてメンバの所持で処理を分岐させるとSFINAEフレンドリでトンチンカンなことをする可能性があるという
これらの動作は Boost.MPL ではどちらも mpl::quoteN が担っているけど、SFINAEフレンドリなメタ関数(不正な引数の場合 type メンバが定義されない)の場合挙動が混乱しやすいので、sprout では quote と self に分けることにした。
— 狂える中3女子ボレロ村上/陶芸C++er (@bolero_MURAKAMI) 2014, 8月 18
quote<T>::apply<int>::type//T::typeがあるならT<int>::type、無いならT<int>
SFINAEフレンドリとは例えばcommon_typeなどで適合する型がなかったときコンパイルエラーで落とすのではなくて::typeをそもそも作らないようにしてSFINAEで弾けるようにしよう。という考えらしい
これをselfごっちゃなquoteにぶち込むとSFINAEフレンドリによってcommon_type::typeが返されるかもしれないし、common_typeが返されるかもしれない。結果としてトンチンカンなことになる
apply<quote<common_type>,a,b,c>::type
//適当な型かもしれないしcommon_type<a,b,c>かもしれない
よってselfとquoteは分けられるべきである、とのことらしい
*1:俺俺MPLであるOTMPではliftとして実装されている。mpl11でもliftだった気がする