【C++ Advent Calendar 2012】Sprout を使うたった一つの理由【2日目(前編)】

C++ Advent Calendar 2012 2日目の記事…なんですが、書きたいことが2つあったので2部作にしてしまいましたてへぺろ(・ω<)
と、いう事で本記事は前編になります。


後編はこちら。

概要

constexpr ライブラリである Sprout の布教を簡単に。

動作環境

本記事内では下記の環境で動作テストを行なっています。

constexpr とは

constexpr とは C++11 に追加されたキーワードで、constexpr を指定することでコンパイル時定数として扱われます。
例えばこんな感じで使うことが出来ます。

template<int N>
struct X{
    static const int value = N;
};

constexpr int
fact(int n){
    return n == 1 ? 1 : fact(n - 1) * n;
}

// コンパイル時に評価される
static constexpr auto result = fact(5);
static_assert(result == 120, "");

// テンプレート引数として渡すこともできる
static_assert(X<fact(5)>::value == result, "");

// 実行時に使用することもできる
int input = 0;
std::cin >> input;
std::cout << fact(input) << std::endl;


上記のようなことをする場合、C++03 では template を利用していましたが、C++11 からは普通の関数として定義ことができるようになりました。
今後 C++11 では、型を template 、値を constexpr で処理していくのが主流となってくと思います。
constexpr についての詳しい下記のサイトを参照してみてください。

constexpr の欠点

constexpr の欠点、というか残念なことに標準ライブラリのほとんどが constexpr に対応していません。
gcc に関しては独自拡張して constexpr に対応していたりしますが。
例えば次のコードは C++11 の規格ではエラーになってしまいます。

#include <tuple>
#include <array>
#include <string>
#include <cmath>

int
main(){
    constexpr std::array<int, 3> v{{1, 2, 3}};

    // std::array は constexpr に未対応
    static_assert(v[0] == 1, "");
    static_assert(v.size() == 3, "");
    
    constexpr std::tuple<int, float> t{3, 2.25f};

    // std::get も constexpr ではない
    static_assert(std::get<0>(t) + std::get<1>(t) == 5.25f, "");

    // math も無理ぽよ
    static_assert(std::sin(10) / std::cos(10) == std::tan(10), "");
    
    // 文字列は言わずもがな
    constexpr std::string str1 = "homu";
    constexpr std::string str2 = "mado";
    static_assert(str1 + str2 == "homumado", "");
    
    return 0;
}


constexpr で処理を書く上で標準ライブラリが constexpr に対応していないのはやはりちょっと残念な感じです。
と、いうことで Sprout の出番です!!

Sprout とは

自称中3女子である id:boleros さんが作成されたコンパイル時に処理される constexpr ライブラリです。
コンパイル時にレイトレーシングという頭のおかしいライブラリといえば聞いたことがある人もいると思います。

実際に使ってみる

コンパイルレイトレーシングのようなワケワカランモノはさておき、Sprout にはコンテナやアルゴリズム、数学、構文解析などなど基本的に必要なライブラリはひと通り揃っています。
Sprout が実装しているライブラリの詳細はここら辺を参照して下さい。
そんな訳で上記のコードを Sprout で書いた場合はこんな感じになります。

#include <sprout/tuple.hpp>
#include <sprout/array.hpp>
#include <sprout/string.hpp>
#include <sprout/math.hpp>

int
main(){
    constexpr sprout::array<int, 3> v{{1, 2, 3}};
    static_assert(v[0] == 1, "");
    static_assert(v.size() == 3, "");
    
    constexpr sprout::tuple<int, float> t{3, 2.25f};
    static_assert(sprout::get<0>(t) + sprout::get<1>(t) == 5.25f, "");

    static_assert(sprout::sin(10) / sprout::cos(10) == sprout::tan(10), "");

    constexpr auto str1 = sprout::to_string("homu");
    constexpr auto str2 = sprout::to_string("mado");
    static_assert(str1 + str2 == "homumado", "");
    
    return 0;
}


基本的には std から sprout に変わっただけですね。
使い方は標準ライブラリや Boost と殆ど変わりません。
コンパイル時の文字列の処理も比較的簡単に扱うこともできます。
すばらしい。

Sprout で FizzBuzz

Sprout でコンパイルFizzBuzz を行う場合こんな感じになります。

#include <sprout/string.hpp>
#include <sprout/array.hpp>
#include <sprout/operation.hpp>
#include <sprout/range/adaptor/transformed.hpp>
#include <sprout/range/adaptor/counting.hpp>


struct fizzbuzz{
    constexpr decltype(sprout::to_string(0))
    operator ()(int n) const{
        return n % 15 == 0 ? sprout::to_string("FizzBuzz")
                 : n %  3 == 0 ? sprout::to_string("Fizz")
                 : n %  5 == 0 ? sprout::to_string("Buzz")
                 : sprout::to_string(n);
    }
};


int
main(){
    static_assert(fizzbuzz()(1) == "1", "");
    static_assert(fizzbuzz()(2) == "2", "");
    static_assert(fizzbuzz()(3) == "Fizz", "");
    static_assert(fizzbuzz()(5) == "Buzz", "");
    static_assert(fizzbuzz()(15) == "FizzBuzz", "");
    
    namespace a = sprout::adaptors;
    
    static constexpr auto result = a::counting(1, 15) | a::transformed(fizzbuzz());

    static_assert(result[ 0] == "1", "");
    static_assert(result[ 1] == "2", "");
    static_assert(result[ 2] == "Fizz", "");
    static_assert(result[ 3] == "4", "");
    static_assert(result[ 4] == "Buzz", "");
    static_assert(result[ 5] == "Fizz", "");
    static_assert(result[ 6] == "7", "");
    static_assert(result[ 7] == "8", "");
    static_assert(result[ 8] == "Fizz", "");
    static_assert(result[ 9] == "Buzz", "");
    static_assert(result[10] == "11", "");
    static_assert(result[11] == "Fizz", "");
    static_assert(result[12] == "13", "");
    static_assert(result[13] == "14", "");
    static_assert(result[14] == "FizzBuzz", "");
    
    return 0;
}


何をやっているのか一目瞭然ですね。
Sprout では Range Adaptor も実装されているのでだいぶすっきりしています。
to_string も constexpr 関数として提供されているので FizzBuzz の処理も簡単に記述することが出来ます。

コンパイル時に文字列をパースしよう

Sprout にはコンパイル時に文字列をパースする Boost.Spirit.Qi ライクな Sprout.Weed というライブラリが提供されています。
これを使用すれば比較的簡単にコンパイル時に構文解析を行うことが出来ます。
例えば次のように __TIME__ マクロをコンパイル時にパースする事が出来たりします。

#include <sprout/weed.hpp>
#include <sprout/string.hpp>
#include <boost/mpl/print.hpp>
#include <boost/mpl/int.hpp>


int
main(){
    namespace weed = sprout::weed;
    using weed::int_;

    static constexpr auto time = sprout::to_string(__TIME__);

    static constexpr auto result = weed::parse(time.begin(), time.end(),
        int_ >> ':' >> int_ >> ':' >> int_
    );
    
    // result.success() でパースが成功したかのチェック
    static_assert(result.success(), "failed parse");
    
    // result.attr() でパース結果を取得
    // 今回のパーサでは sprout::array<long long int, 3> が返ってくる
    // パーサによっては sprout::tuple が返ってくる場合もあるので注意
    static constexpr sprout::array<long long int, 3> result_attr = result.attr();
    static constexpr auto hour    = result_attr[0];
    static constexpr auto minutes = result_attr[1];
    static constexpr auto second  = result_attr[2];

//  static_assert(hour    == 14, "");
//  static_assert(minutes ==  3, "");
//  static_assert(second  == 51, "");

    // mpl::print を使用してコンパイル時に出力したり
    namespace mpl = boost::mpl;
    typedef mpl::print<mpl::int_<hour   >>::type hourT;
    typedef mpl::print<mpl::int_<minutes>>::type minutesT;
    typedef mpl::print<mpl::int_<second >>::type secondT;
    
    return 0;
}


コンパイル時に __TIME__ がパースできるとか面白いですね。
これと static_assert 利用すれば特定の時間のみコンパイルが出来る。みたいなことができますね!!
__TIME__ 以外にも __DATE__ や __FILE__ なんかをパースしてみても面白いかと思います。
もちろん、マクロではなくて、直接文字列を指定して使用することも出来ます。
動的配列が使用できないのでちょっと癖は強いんですが、Boost.Spirit.Qi を使ったことがあればそこまで使うのは難しくないかと思います。

Sprout を使う理由

さて、表題の Sprout を使う理由ですが、やはりホット(某C++erではなく)で面白いライブラリだからというのが大きいです。
これだけ変なことをしている constexpr ライブラリは他にはないんじゃないでしょうか。
また、開発も割りとコンスタントに行われており、気がつくと新しいライブラリが実装されている、という事が多いです。
バグ報告を行なってからの対応や修正も迅速に行なってくれますし嬉しい限り。
constexpr というニッチな層ではあるんですが、多数のライブラリが提供されているので使ってみる価値は十分にあると思います。


まぁぶっちゃけ、使っている人が増えればフィードバック(資料)も増える→わたしが嬉しいっていうのが一番の理由だったりするんですけどね!!
また、多くの人に利用されることで作者のモチベーションにつながり、新しい使い方が発見されたりするのではないでしょうか。
もっと盛り上がれ Sprout。
(たった一つではないという事にツッコんではいけない。

まとめ

Sprout 便利なので使いましょう。

[宣伝]

不定期ですが、オンライン上で読書会を開いています。


気になる方はぜひ参加してみてはどうでしょうか!