Hatena::ブログ(Diary)

C++でゲームプログラミング

2011-12-06

[][]【Boost Advent Calendar 2011】phoenix::progress_display【6日目】

これは Boost Advent Calendar 2011 6日目の記事です。


さて、個人的に3回目の Advent Calendar です。

Boost Advent は個人的に気になっている Boost.TTI Boost.QVM について書こうと思ったんですが、時期的に今しかチャンスが無いのでネタに走ります。

progress_display は滅びぬ、何度でも蘇るさ。


[Boost.Phoenix とは]

Boost.Phoenix は、Boost 1.47.0 で公開されたライブラリです。

Boost.Lambda と同様に 無名関数の定義する事が出来ます。

元々は Boost.Spirit のサブライブラリとして、公開されていましたが、Boost 1.47.0 から V3 という形で公開されました。

基本的な使い方は Boost.Lambda と変わらないので Boost.Lambda を使ったことがあれば、使い方はそんなに難しくないと思います。


#include <boost/phoenix.hpp>
#include <boost/range/irange.hpp>
#include <boost/range/adaptor/filtered.hpp>

using boost::phoenix::arg_names::arg1;
using boost::phoenix::arg_names::arg2;

arg1(1, 2, 3);       // == 1
arg2(1, 2, 3);       // == 2
(arg1 + arg2)(3, 2); // == 5

using boost::adaptors::filtered;
boost::irange(0, 9)|filtered(arg1 % 2 == 0);
// => 0, 2, 4, 6, 8

また、ユーザ側で簡単に Boost.Phoenix の構文としてアダプトすることも出来ます。

#include <boost/phoenix/function/adapt_function.hpp>

int
minus_impl(int a, int b){
    return a - b;
}

// Boost.Phoenix へアダプト
BOOST_PHOENIX_ADAPT_FUNCTION(int, minus, minus_impl, 2);

minus(arg1, arg2)(6, 2);    //  == 4

// アダプトしない場合は bind を使用
boost::phoenix::bind(&minus_impl, arg1, arg2)(6, 2);    // == 4

ということで、この不死鳥の名を持つライブラリで progress_display を復活させようではありませんか!


[phoenix::progress_display]

#include <boost/progress.hpp>
#include <boost/thread.hpp>

#include <boost/phoenix/core.hpp>
#include <boost/phoenix/operator.hpp>
#include <boost/phoenix/function.hpp>


BOOST_PHOENIX_DEFINE_EXPRESSION(
    (boost)(phoenix)(progress_display),
    (boost::phoenix::meta_grammar)    // Count
    (boost::phoenix::meta_grammar)    // OS
    (boost::phoenix::meta_grammar)    // s1
    (boost::phoenix::meta_grammar)    // s2
    (boost::phoenix::meta_grammar)    // s3
    (boost::phoenix::meta_grammar)    // Do
)

namespace boost{

namespace phoenix{

struct progress_display_eval{
    typedef void result_type;
    
    template<
        typename Count,
        typename OS,
        typename S1,
        typename S2,
        typename S3,
        typename Do,
        typename Context
    >
    result_type
    operator ()(
        Count const& count,
        OS& os,
        S1 const& s1,
        S2 const& s2,
        S3 const& s3,
        Do const& do_,
        Context const ctx
    ) const{
        boost::progress_display show_progress(
            boost::phoenix::eval(count, ctx),
            boost::phoenix::eval(os, ctx),
            boost::phoenix::eval(s1, ctx),
            boost::phoenix::eval(s2, ctx),
            boost::phoenix::eval(s3, ctx)
        );
        for(unsigned long i = 0 ; i < boost::phoenix::eval(count, ctx) ; ++i){
            boost::phoenix::eval(do_, ctx);
            ++show_progress;
        }
    }
};

template<typename Dummy>
struct default_actions::when<rule::progress_display, Dummy>
    : call<progress_display_eval, Dummy>{};

template<
    typename Count,
    typename OS,
    typename S1,
    typename S2,
    typename S3
>
struct progress_display_gen{
    progress_display_gen(
        Count const& count,
        OS os,
        S1 const& s1,
        S2 const& s2,
        S3 const& s3
    )
    : count(count)
    , os(os)
    , s1(s1)
    , s2(s2)
    , s3(s3)
    {}
    
    template<typename Do>
    typename expression::progress_display<Count, OS, S1, S2, S3, Do>::type const
    operator [](Do const& do_) const{
        return expression::progress_display<Count, OS, S1, S2, S3, Do>::make
            (count, os, s1, s2, s3, do_);
    }
private:
    Count const& count;
    OS os;
    S1 const& s1;
    S2 const& s2;
    S3 const& s3;
};

template<typename Count>
inline
progress_display_gen<
    Count,
    boost::reference_wrapper<std::ostream>,
    std::string, std::string, std::string
> const
progress_display(
    Count const& count,
    std::ostream& os = std::cout,
    std::string const& s1 = "\n",
    std::string const& s2 = "",
    std::string const& s3 = ""
){
    return progress_display_gen<
        Count,
        boost::reference_wrapper<std::ostream>,
        std::string, std::string, std::string
    >(count, boost::ref(os), s1, s2, s3);
}

}  // namespace phoenix

}  // namespace boost


#include <boost/phoenix/bind.hpp>
#include <boost/phoenix/stl.hpp>
#include <boost/phoenix/operator.hpp>
#include <boost/phoenix/scope/let.hpp>
#include <boost/phoenix/function.hpp>
#include <boost/phoenix/function/adapt_function.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/function.hpp>
#include <iostream>
#include <vector>
#include <string>


int
fibonacci_impl(int n){
    return n == 0 ? 0
         : n == 1 ? 1
         : fibonacci_impl(n - 1) + fibonacci_impl(n - 2);
}

BOOST_PHOENIX_ADAPT_FUNCTION(int, fibonacci, fibonacci_impl, 1);


template<typename Target>
struct lexical_cast_impl{
    typedef Target result_type;

    template<typename Source>
    result_type
    operator ()(Source const& source) const{
        return boost::lexical_cast<Target>(source);
    }
};

template<typename Target, typename T>
typename boost::result_of<
    boost::phoenix::function<lexical_cast_impl<Target> >(T const&)
>::type
lexical_cast(T const& t){
    return boost::phoenix::function<lexical_cast_impl<Target> >()(t);
}

BOOST_PHOENIX_ADAPT_FUNCTION(void, sleep, boost::this_thread::sleep, 1);

int
main(){
    namespace phx = boost::phoenix;
    using boost::phoenix::arg_names::arg1;
    using phx::local_names::_n;

    // 
    // [構文]
    //  boost::phoenix::progress_display(count, ostream, s1, s2, s3)[
    //      statement,
    //      ...
    //  ]
//  using boost::phoenix::arg_names::arg1;
//  auto f = boost::phoenix::progress_display(arg1)[
//      // arg1 回実行される
//      sleep(boost::posix_time::milliseconds(100))
//  ];
//  f(100ul);

    
    // 
    // FibBuzz を処理してみる
    // 
    std::vector<std::string> result;
    int i = 1;
    boost::function<void(unsigned long)> f = boost::phoenix::progress_display(
        arg1,
        std::cout,
        "FibBuzz:",
        "        ",
        "        "
    )[
        phx::let(_n = fibonacci(phx::ref(i)))[
            phx::push_back(phx::ref(result),
                phx::if_else(_n % 15 == 0, "FizzBuzz",
                phx::if_else(_n %  5 == 0, "Fizz",
                phx::if_else(_n %  3 == 0, "Buzz",
                ::lexical_cast<std::string>(_n)
            ))))
        ],
        ++phx::ref(i),
        // それっぽく見せるための wait
        sleep(boost::posix_time::milliseconds(100))
    ];
    f(40ul);
    
    std::cout << "result:" << std::endl;
    boost::for_each(result, std::cout << arg1 << ", ");
    return 0;
}

[出力]

f:id:osyo-manga:20111205235145g:image


こんな感じで Boost.Phoenix を使えば簡単にオレオレ構文を定義することが出来ます。

やったね☆(ゝω・)vキャピ

単に progress_display をラップしただけとは言ってはいけない


あ、ちなみに『バン(∩`・ω・)バンバンバンバン゙ン』は、Vim で出力されているのでプログラムとは関係ありません。

かわいいですね。


[まとめ]

構文を定義する場合は、Boost.Phoenix の各 statement(for_ とか while_ とか)のコードが参考になると思います。

Boost.Phoenix用法用量を守って正しくお使い下さい。


[次回予告]

明日の Boost Advent Calendar 2011 は、@fjnli さんです。

明日を楽しみにして1日を過ごしましょう:)

ところで、Boost Advent は2週目をやってもいいのかしら。


[動作確認]

  • boost 1.48.0
  • gcc 4.7
  • clang 3.0
  • msvc 2010

[参照]