Boost.Spirit.Qi セマンティックアクションで正当性チェック

Boost.Xpressiveではcheckで正当性チェックができるのにBoost.Spirit.Qiではできないのかなーと思ったらできるようです。まずは関数指定をする基本的な書き方:

#include <iostream>
#include <string>
#include <boost/spirit/include/qi.hpp>
 
namespace qi = boost::spirit::qi;
 
void fail(int x, boost::spirit::unused_type, bool& pass)
{
    pass = x == 2;
}
 
int main()
{
    const std::string s = "3";
 
    std::string::const_iterator it = s.begin();
    if (!qi::parse(it, s.end(), qi::int_[fail])) {
        std::cout << "fail" << std::endl;
    }
    else {
        std::cout << "success" << std::endl;
    }
}

ドキュメントを見てもよくわからなかったので、libs/spirit/test/qi/action.cppを見たらそれらしいコードがあったのでした。参照で渡されるpassパラメータをfalseにすればマッチ失敗として扱われます。
ここでは、"2"だったらsuccess、それ以外はfailになります。


次に、プレースホルダを使って書いてみます。PhoenixではなくBoost.Lambdaを使いました。

#include <iostream>
#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/lambda/lambda.hpp>
 
namespace qi = boost::spirit::qi;
using namespace boost::lambda;
 
int main()
{
    const std::string s = "3";
 
    std::string::const_iterator it = s.begin();
    if (!qi::parse(it, s.end(), qi::int_[_3 = _1 == 2])) {
        std::cout << "fail" << std::endl;
    }
    else {
        std::cout << "success" << std::endl;
    }
}

_1は許せるけど、_3って書いても何者なのかわからないですね・・・。切り出しましょう。

#include <iostream>
#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/lambda/lambda.hpp>
 
namespace qi = boost::spirit::qi;
using namespace boost::lambda;
 
template <class Expr>
struct valid_check_t {
    Expr expr;
 
    valid_check_t(const Expr& expr_)
        : expr(expr_) {}
 
    template <class T>
    void operator()(const T& x, boost::spirit::unused_type, bool& pass) const
    {
        pass = expr(x);
    }
};
 
template <class Expr>
valid_check_t<Expr> valid_check(const Expr& expr)
{
    return valid_check_t<Expr>(expr);
}
 
int main()
{
    const std::string s = "3";
 
    std::string::const_iterator it = s.begin();
    if (!qi::parse(it, s.end(), qi::int_[valid_check(_1 == 2)])) {
        std::cout << "fail" << std::endl;
    }
    else {
        std::cout << "success" << std::endl;
    }
}

これでだいぶ使いやすくなりました。