C++ の web フレームワーク crow のコンパイル時処理

最近,C++ の crow という薄いウェブフレームワークが出ました.特に要件は書いていないですが,C++11 以上が必須のモダンな設計で Python の Flask のような雰囲気で書けるのが売りらしいです.

ipkn/crow

CROW_ROUTE(app,"/hello/<int>")
   ([](int count){
       if (count > 100)
           return crow::response(400);
       std::ostringstream os;
       os << count << " bottles of beer!";
       return crow::response(os.str());
   });

このように,ルートを指定し,それに対する処理をラムダ式で書くようです.ルート指定内にあるタグに対応した引数を取るラムダ式を指定します. examples の中にはありませんが,"/users/<int>/<str>" のようにタグを複数指定することも可能です.

これをみたときにちょっと気になったのが,全体的にマクロを使わない感じで設計されているのに,(内部実装は別として)どうして CROW_ROUTE というマクロがユーザの見えるところに必要なのかちょっと気になりました.そこでマクロ定義を覗いてみると

#define CROW_ROUTE(app, url) app.route<crow::black_magic::get_parameter_tag(url)>(url)

と定義されています.ルート指定に使われた文字列はコンパイル時処理によってテンプレートパラメータになるようです.名前空間名が black_magic だったので,ちょっと気になって実装を見てみました.black_magic 名前空間crow/utility.h に定義されています.

簡単なリテラル型の固定長文字列クラスと,そのタグパーサがあり,ルートから(複数あるかもしれない)タグを切り出してきて,タグの型に応じて一意の値を生成します.この値が template<int I> app::route() メンバ関数テンプレートのテンプレート引数に渡るわけです.

black_magic 名前空間の最後では get_parameter_tag() で生成された値から型に戻す(e.g. <int> タグから生成された値を int 型へ変換する)テンプレートメタ関数を定義しています.get_parameter_tag() とは逆の演算をやるだけの簡単な中身です.

crow ではこのようにして,コンパイル時にルートを示す文字列のパースと型への変換を行うことで,

  1. 不正なルート指定(たとえば > の無いタグ)が無いかをコンパイル時に調べ,コンパイル時エラーに落とす
  2. ハンドラとして指定されたラムダ式の引数が正しいかをコンパイル時に調べ,コンパイル時エラーに落とす

などの処理を行っているようです.

大したことはしていないので,軽くて実用的なコンパイル時処理の例として気になる方はさっと目を通してみると良いかもしれません.

ちなみに,僕は試していませんが,こういった文字列のパースは Sprout.Weedコンパイル時に評価可能なパーサジェネレータ)を使うのが便利そうです.

また,C++14 で constexpr の制限緩和が来ているので,C++14 ならさらに簡単に書けるようになると思います.