try to use boost.mpl
Post on 10-May-2015
1.426 Views
Preview:
TRANSCRIPT
Boost.MPL をつかってみよう
2013/06/22 Sat.
Boost 勉強会12 #大阪
目次
● 自己紹介
● はじめに
● Boost.MPL
● おまけ
● まとめ
自己紹介
自己紹介● すいかばー (@suibaka)
● 17歳(高校2年生)
● 2年前にC++と運命の出会い
● 刺激(×まさかり)を求めて
Boost勉強会へ初参加
● よく大学生に間違われます
(実際おっさん)
● スイカバーおいしい!!
Introductionはじめに
今!!constexprが
ですが
constexprにもできないことがあります。
それは型を扱うことです。
これからはTMPとconstexprを共存させ、メタプログラミングをやっていくことが大切です。
今日はTMPに焦点を当て、その時にちょっと便利なBoost.MPLの話をします
What's Boost.MPL?
名前の通り、TMPを支援してくれる便利なライブラリ。Boostの内部実装や、色んなところで使われています。
例えば…● mpllibs (https://github.com/sabel83/mpllibs)
コンパイル時に構文解析とか出来るライブラリ
諸注意
以降、例として出てくるコードは以下のものが省略されています。
namespace mpl = boost::mpl;using namespace mpl::placeholders;その他必要な #include
Metafunction普通の関数が引数をとって戻り値を返すのに対し、メタ関数はテンプレートパラメータを受け取ってクラス内のtypedefを結果とする。
template <typename T>struct add_ptr { using type = T*; };
// 引数 int で add_ptr を呼び出すusing res = add_ptr<int>::type;// res == int*BOOST_STATIC_ASSERT(( std::is_same<res, int*>::value ));
Metafunction forwardingメンバにtypeがあるmetafunctionをpublic継承するテクニックのこと。
template <typename T>struct is_int : std::false_type{};template <>struct is_int<int> : std::true_type{};
BOOST_STATIC_ASSERT((!is_int<char>::value));BOOST_STATIC_ASSERT((is_int<int>::value));
Boost.MPL
Integral/Boolean Wrapper
using int_zero = mpl::int_<0>;using integral_one = mpl::integral_c<long, 1>;using true_ = mpl::bool_<true>; // MPLにこのまま定義されてる
BOOST_STATIC_ASSERT(( int_zero::value == long_zero::value ));BOOST_STATIC_ASSERT(( true_::value ));
Comparison Operators左右でそれぞれ対応した意味を持つ。
mpl::equal_to<A, B>::value A::value == B::value
mpl::not_equal_to<A, B>::value A::value != B::value
mpl::greater<A, B>::value A::value > B::value
mpl::greater_equal<A, B>::value A::value >= B::value
mpl::less<A, B>::value A::value < B::value
mpl::less_equal<A, B>::value A::value <= B::value
equal_to<A,B>::type::value ともかけるが、これはequal_to<A,B>::value と同じ意味。(equal_toの::typeは自身の型を返すため)。
If statement
template <typename T>struct param_type : mpl::eval_if< mpl::or_<std::is_scalar<T>, std::is_reference<T>> , mpl::identity<T> , boost::add_reference<T> >{};
BOOST_MPL_ASSERT((std::is_same<param_type<int>::type, int>));BOOST_MPL_ASSERT(( std::is_same<param_type<std::string>::type, std::string&>));
template <typename T>using is_pointer_or_reference = mpl::or_<std::is_pointer<T>, std::is_reference<T>>;
BOOST_STATIC_ASSERT(( is_pointer_or_reference<int*>::value ));BOOST_STATIC_ASSERT(( !is_pointer_or_reference<char>::value ));
mpl::not_<metafunction>mpl::or_<metafunction1, metafunction2, ..., metafunctionN>mpl::and_<metafunction1, metafunction2, ..., metafunctionN>
引数に::valueを持つmetafunctionを渡す。::valueでその結果が得られる。
Sequences
mpl::vector<char, int, double>mpl::list<short, long, float>mpl::map< // mapはmpl::pairを要素に持つ mpl::pair<int, unsigned int> , mpl::pair<double, unsigned double>>
list, vector, deque, map, setMPLのSequenceは型が要素となる。
Integral Sequence Wrappers
mpl::list_c<T, 1, 3, ..., 9>
数値型(mpl::integral_c)を格納するために特化したSequence。list_c, vector_c, set_cが存在する。
mpl::list< mpl::integral_c<T, 1> , mpl::integral_c<T, 3> , ... , mpl::integral_c<T, 9>>
上記のコードは以下と同じ意味
range_c
mpl::vector< mpl::integral_c<T, N> , mpl::integral_c<T, N+1> , ... , mpl::integral_c<T, M-2> , mpl::integral_c<T, M-1> // NからM-1まで>
連続した数値型のリスト。じつは遅延評価。range_c<T, N, M> で下記のようなイメージ※あくまでもイメージです。ホントは違います
Iterators
using v = mpl::vector<char, int, double>;using int_pos = mpl::find<v, int>::type; // int_posがiteratorusing result = mpl::deref<int_pos>::type; // int_posの参照剥がしBOOST_STATIC_ASSERT(( std::is_same<result, int>::value ));
std::vector<int> v = { 1, 2, 3, 4 };auto three_pos = std::find( std::begin( v ) , std::end( v ) , 3 );int result = *three_pos;assert( result == 3 );
上記のコードは以下のコードととても似ている。
category
Sequenceには種類があり、できることやできないことが決まっている。● Forward Sequence● Bidirectional Sequence● Random Access Sequence● Extensible Sequence● Front Extensible Sequence● Back Extensible Sequence● Associative Sequence● Associative Extensible Sequence
Iteratorも同様。● Forward Iterator● Bidirectional Iterator● Random Access Iterator
もっと詳しく知りたい人はBoost.MPLのリファレンスを見ましょう。
Sequences -concepts-
using v = mpl::push_back<mpl::vector<int>, char>::type;using m = mpl::insert<mpl::map<>, mpl::pair<char, int>>::type;
BOOST_MPL_ASSERT((mpl::equal<v, mpl::vector<int, char>));BOOST_MPL_ASSERT((mpl::equal<m, mpl::map<mpl::pair<char, int>>));
一応簡単に。STLのコンテナにそれぞれ特徴があるのと同じ。※MPLの場合はメンバ関数ではない。
→とりあえず vector(テンプレ
Algorithms結果は型として返される。
using v = mpl::vector<int, long>;using res = mpl::transform<v, boost::add_pointer<_>>::type;
BOOST_MPL_ASSERT((mpl::equal<res, mpl::vector<int*, long*>>));
using types = mpl::vector<short, char, double, long>;using max_size_it = mpl::iter_fold< types , mpl::begin<types>::type , mpl::if_< mpl::less<mpl::sizeof_<mpl::deref<_1>> , mpl::sizeof_<mpl::deref<_2>>> , _2, _1 >>;BOOST_MPL_ASSERT((std::is_same<mpl::deref<max_size_it>::type, double>));
Algorithms
mpl::find<seq, T> 型の検索。なければmpl::end<seq>mpl::find_if<seq, pred> 述語を使った検索。後はfindと同じmpl::contains<seq, T> seqにTが含まれるかどうかmpl::count<seq, T> Tがいくつ含まれているかmpl::count_if<seq, pred> predを満たすものが含まれている数mpl::equal<seq1, seq2> seq1とseq2の要素が同じかmpl::lower_bound<seq, T, pred> 最初にpredが満たされたところmpl::upper_bound<seq, T, pred> 最後にpredが満たされたところmpl::max_element<seq> 要素で最大のものを得るmpl::min_element<seq> 要素中で最小のものを得る
Metafunction ::type で得られる結果
Algorithms
mpl::copy<seq> 同じ要素を持った型mpl::copy_if<seq, pred> predを満たすものを持った型mpl::remove<seq, T> seqからTを削除した型mpl::remove_if<seq, pred> predを満たす型を削除した型mpl::replace<seq, old, new> seqのoldの要素をnewに置換した型mpl::replace_if<seq, pred, new> predを満たす型をnewに置換した型mpl::reverse<seq> seqの要素を逆順にした型mpl::transform<seq, op> seqの各要素にopを適用した型mpl::transform<seq1, seq2, op> opが2項であること以外は上と同じmpl::unique<seq> 重複要素を削除した型
Metafunction ::type で得られる結果
inserter
template <typename State, typename Operation>struct inserter{ using state = State; using operation = Operation;};
state:操作中のSequenceの状態operation:stateから新しいstateを作るのに使われる
inserter
using v = mpl::vector<char, int>;using res = mpl::copy< mpl::list<long, double> , mpl::back_inserter<v> // inserter<v, push_back<_>> >::type;
BOOST_STATIC_ASSERT(( mpl::equal< res, mpl::vector<char, int, long, double> >::value));
inserterの使い方。vectorにlistの要素を追加する。
inserter
using v = mpl::vector<char, int>;using res = mpl::copy< mpl::list<long, double> , mpl::back_inserter<v> // inserter<v, push_back<_>> >::type;
BOOST_STATIC_ASSERT(( mpl::equal< res, mpl::vector<char, int, long, double> >::value));
inserterの使い方。vectorにlistの要素を追加する。
シーケンスが違ってもOK
Sequence Views
Sequenceを操作するAlgorithm(e.g., mpl::transform)をより強力にしたもの。
何が強力?例えば、transform_view<S,Pred>は必要になるまでSequenceの各要素にPredを適用しない。いわゆる遅延評価。
transform_viewusing seq1 = mpl::vector<int, char, volatile float>;using seq2 = mpl::list<double, short, long>;template <typename Seq>using contains_float = mpl::contains< mpl::transform_view< Seq , boost::remove_cv<_> > , float>;
BOOST_MPL_ASSERT(( contains_float<seq1> ));BOOST_MPL_ASSERT_NOT(( contains_float<seq2> ));
pair_view// 2つのSequenceの各要素をmpl::pairでくっつけるusing l = mpl::list<char, int, short>;using v = mpl::vector<long, float, double>;using view = mpl::pair_view<l, v>;using first = mpl::begin<view>::type;using it = mpl::advance<first, mpl::int_<2>>::type;
BOOST_MPL_ASSERT(( std::is_same< mpl::deref<it>::type , mpl::pair<short, double> >));
single_view/joint_view// 任意の型Tを一要素のシーケンスとして表現using view = mpl::single_view<int>;
BOOST_MPL_ASSERT(( std::is_same<mpl::begin<view>::type, int> ));
// 2つのSequenceを連結using v = mpl::vector<char, int>;using l = mpl::list<float, double>;using view = mpl::joint_view<v, l>;
BOOST_MPL_ASSERT(( mpl::equal< mpl::vector<char, int, float, double>, view> ));
filter_view
// ポインタだけ選りすぐるusing seq = mpl::vector<char&, long*, double, int*&>;using res = mpl::transform_view< mpl::filter_view<seq, boost::is_pointer<_1>> , boost::remove_pointer<_1>>;
using excepted = mpl::vector<long>;BOOST_MPL_ASSERT(( mpl::equal<excepted, res> ));
自分でSequenceを書く
MPLのシーケンスと同じように設計することで、MPLの有用なAlgorithmをそのまま利用できます。
具体的には…● 作りたいシーケンスのタグを作り、そのシーケンスにtagと
いう名前でtypdefする● アルゴリズムの実装メタファンクション(e.g.,
mpl::clear_impl, mpl::push_back_impl ...)をそのタグで特殊化し、実装する
● あとは普段通りアルゴリズム呼び出すだけ!
おまけTMPで使われるルール&テクニック
SFINAE
template <typename T>struct has_f{ template <typename U> static auto check(U x) -> decltype(x.f(), std::true_type()); static std::false_type check(...);
public: static const bool value = decltype(check(std::declval<T>()))::value;};
// 続く...
テンプレートの実体化に失敗してもエラーにしないルール。実体化→オーバーロード解決の順に行われるので、失敗したものをオーバーロードの候補から取り除ける。
SFINAEtemplate <typename T>auto g(T x) -> decltype(x.f()){ std::cout << "has f" << std::endl; return x.f();};template <typename T, typename = typename std::enable_if<!has_f<T>::value>::type>void g(T x){ std::cout << "does not has f" << std::endl;}
struct A {};struct B { int f() { return 1; } };int main(){ A a; B b; g(a), std::cout << g(b) << std::endl;}
does not have fhas f1
要件を満たさない関数の削除
void f(int) = delete; // #1template <typename T>void f(T t) {} // #2template <typename T> requires character<T>() // char, wchar_t, ...void f(T t) {} = delete; // #3 C++11では動きません
int main(){ f(0.0); // OK f('a'); // Error! f(0); // Error!}
ある条件を満たす場合以外、その関数を削除してコンパイルエラーにすることができます。
Variable Templates(C++14)
template <Num N>constexpr N min = std::numeric_limits<T>::min();
std::cout << min<int> << std::endl;std::cout << min<unsigned> << std::endl;
変数にもtemplateを指定できるようになる
Concept
template <typename T>concept bool equality_comparable(){ return requires (T a, T b) { {a == b} -> bool; {a != b} -> bool; };}
template <typename T> requires equality_comparable<T>()void f(T a, T b) { ... };
いずれ入るであろうconcept。
Concept
concept ってどういう意味があるの?
● constexprである● 特殊化されない● 定義を必ず持つ● type specifier(型指定子)として使用される(可能性があ
る
Concept
Conceptが入るとtemplateのオーバーロード解決が少し変わる
テンプレート引数の推論
constraints(制約)の実体化とそれが有効かどうかの確認
宣言を実体化する
残った候補の中から最もマッチしたもの(特殊化、制約)を選択
Concept
[x]<equality_comparable T>(T y) { return x == y; }// 又は[x](equality_comparable y) { return x == y; }
ラムダにも使える?らしい
template <input_iterator I>void advance(I& it);template <bidirectional_iterator I>void advance(I& it);template <random_access_iterator I>void advance(I& it);
オーバーロードとかもできるらしい
実際の使用例
実際の使われ方はどんな感じ?
冒頭ででてきたmpllibsにsafe_printfというものがあるので、試しにその一部の実装をのぞいてみましょう!
int main(){ using mpllibs::safe_printf::printf;
printf<MPLLIBS_STRING("%s, %d")>("hoge", 11); printf<MPLLIBS_STRING("%s, %d")>(11, "hoge"); // error}
基本となるstring。下記のようにtagを作っておくと、Boost.MPLと連携しやすくなります。
struct string_tag // タグディスパッチするため{ using type = string_tag;};
template <char... Cs>struct string{ using type = string; using tag = string_tag;};
mpllibs safe_printftemplate <class S, char C>struct push_front_ch;template <char... Cs, char C>struct push_front_ch<string<Cs...>, C>
: string<C, Cs...>{};
namespace boost { namespace mpl {template <>struct clear_impl<string_tag> // string_tagで特殊化すると、{ // mpl::clear<string<...>>でこれが呼ばれる using type = clear_impl;
template <class S> struct apply : MPL::string<> {};};}}
次に"hoge"という文字列から'h', 'o', 'g', 'e'を得ていくために、一文字ずつ抜き出してくる関数があります。
#define MPL_NO_CHAR EOF
template <int L, class T>constexpr int string_at(const T (&s)[L], int n){ // 要素外なら MPL_NO_CHAR を返す return n >= L-1 ? MPL_NO_CHAR : s[n];}
mpllibs safe_printf
template <class S>struct remove_no_chars : S{};template <char... Cs>struct remove_no_chars<string<MPL_NO_CHAR, Cs...>> : mpl::clear<string<MPL_NO_CHAR, Cs...>>{};template <char C, char... Cs>struct remove_no_chars<string<C, Cs...>> : push_front_ch<typename remove_no_chars<string<Cs...>>::type, C>{};
mpllibs safe_printf
#define MPL_STRING_MAX_LENGTH 32
#define MPLLIBS_STRING_N(z, n, s) string_at((s), n)#define MPLLIBS_STRING(s) \ remove_trailing_no_chars< \ string< \ BOOST_PP_ENUM(MPLLIBS_STRING_MAX_LENGTH, MPLLIBS_STRING_N, s) \
> \ >::type
まとめ
まとめ
あくまでも…MPLはTMPを補助する立ち位置。使用が目的では無いちょっとだけ使ってみるのも全然あり。使えそうなら使ってみましょう!
使うときは、ユーザーコードにMPLのコードができるだけ現れないようにしましょう。ライブラリ内で完結させたほうがいいと思います。
MPL自体は古いけどまだ使えます
本当は…
コンパイル時のパフォーマンスとか、どうすればもっと改善できるかとかの話もしたかった。→まとめきれませんでしたゴメンナサイ。
参考文献
● Boost.MPL Referencehttp://www.boost.org/doc/libs/1_53_0/libs/mpl/doc/refmanual.html
● N3580http://isocpp.org/blog/2013/03/new-paper-n3580-concepts-lite-andrew-sutton-bjarne-stroustrup-gabriel-dos-r
最後に
Let's enjoy Template Metaprogramming World!
top related