パスワードを忘れた? アカウント作成
302692 journal

Yak!の日記: Boost.Spirit.Qi の auto_ パーサーでカンマ区切りのデータを Fusion シーケンスに突っ込む

日記 by Yak!

nagoya313 さんの記事「Boostのfusionとspiritやばい」で Boost.Spirit.Qi を使ってカンマ区切りのデータを Fusion シーケンスに突っ込む例が紹介されている。これはこれで簡潔なのだがせっかく構造体を Fusion にアダプトしていてメンバの型情報がとれるはずなのに parse() に渡すルールのところでもメンバの型情報を指定しなければならないのが悲しい感じである。どうせならルールを自動生成したいところだ。

実は Boost.Spirit.Qi にはそんなニーズに応えられる機能が存在する。auto_ パーサーである。auto_ パーサーがどういうものかは本家ドキュメントか faith_and_brave さんの記事「qi::auto_で簡易パース」を参照してもらうとして、Fusion シーケンスを属性として渡した場合には、各要素に対する auto_ パーサーを >> で連結したものがシーケンスに対応するパーサーとなる。これが単純に >> で連結したものではなく、>> ',' >> で連結したものになればカンマ区切りデータ用のパーサーが得られることになる。

そのままでも十分変態的な Spirit だが拡張性が非常に高いところも変態的である。当然ながら auto_ パーサーの挙動もカスタマイズ可能だ。これは create_parser という customization point で実現できる。customization point とは特殊化することで Spirit の挙動をカスタマイズできるテンプレートだと思えば良い。create_parser の概要は以下の通りである(ref. 本家ドキュメント)。

template <typename T, typename Enable>
struct create_parser
{
    typedef <unspecified> type;
    static type const& call();
};

T 型の属性を渡した際に返すパーサーの型を type とし、実際に返すための関数が call()、Enable は SFINAE を使って有効か無効かを切り替えるためのもので Spirit の customization point には大抵ついている。基本的にはこの create_parser を T が Fusion シーケンスの場合に対して特殊化してやればよいことになる(ただしデフォルトでは std::string に対しても実装がないのでその分も特殊化が必要である)。

ここで実際の実装を見る前にデフォルトの実装を見てみよう。

    ///////////////////////////////////////////////////////////////////////////
    // Fusion sequences
    template <typename Sequence>
    struct meta_create_sequence
    {
        // create a mpl sequence from the given fusion sequence
        typedef typename mpl::fold<
            typename fusion::result_of::as_vector<Sequence>::type
          , mpl::vector<>, mpl::push_back<mpl::_, mpl::_>
        >::type sequence_type;

        typedef make_nary_proto_expr<
            sequence_type, proto::tag::shift_right, qi::domain
        > make_proto_expr;

        typedef typename make_proto_expr::type type;

        static type call()
        {
            return make_proto_expr::call();
        }
    };

Fusion シーケンスを vector に変換しその結果に対して mpl::fold で push_back を適用する。得られるのは Fusion シーケンスと同じ要素型を持つ mpl::vector である。make_nary_proto_expr は mpl::vector の各要素型に対して create_parser 相当を呼び出しそれらを指定した演算子で連結する。この場合、shift_right = 右シフト、つまり >> である。つまり mpl::vector<A, B, C> に対しては parserA >> parserB >> parserC が返ってくることになる。

これを元に >> ',' >> で連結するにはどうしたらいいか?普通に考えれば make_nary_proto_expr を修正するべきだろうが解析が面倒そうだったので今回は make_nary_proto_expr をそのまま利用することにした。make_nary_proto_expr の結果が parserA >> ',' >> parserB >> ',' >> parserC になれば良いのだから mpl::vector<A, 何か, B, 何か, C> を渡してやれば良い。ここで何かとは create_parser で ',' が返ってくる型である。以上の考え方を元にして実装したのがこのコードだ。10 行目で定義している csv_separator が「何か」である。特殊化した create_parser は 30 行目以降にある。15 行目からの is_fusion_sequence_but_not_proto_expr は Fusion シーケンスに対してだけ create_parser を特殊化するためのものだ。Enabler として 61 行目で使用している。44 行目からは std::string 用の create_parser 特殊化、59 行目からが問題の Fusion シーケンス用の特殊化である。66 行目の fold で単純に要素を push_back するだけではなくさらに csv_separator も push_back している。結果として要素と csv_separator が交互に並んだものが sequence_type_ として得られる。これだけだと末尾に余計な csv_separator が付くため、要素数が 0 でなければ pop_back している(70 行目)。後はデフォルト実装と同様である。これだけ用意しておけば 123 行目のように auto_ に対して Fusion シーケンスを渡してやるだけでそれに適したカンマ区切りのパーサーが得られる。ちなみに 88 行目、101 行目のように BOOST_FUSION_DEFINE_STRUCT によって構造体定義と BOOST_FUSION_ADAPT_STRUCT を同時にできる。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

身近な人の偉大さは半減する -- あるアレゲ人

読み込み中...