

現在 boost::spirit を使って関数電卓を作っています。

四則演算は次のような感じで実装できますが、

`(double_>>'+'>>double_)[_val=_1+_2]`

関数の場合、例えば二つの数の平均を計算する関数などを考えたとき、

`(double_>>','>>double_)[_val=Average(_1,_2)]`

という感じにすることはできますか？


CC BY-SA 3.0
2016-11-19T06:11:25.783
30531
2023-04-25T05:01:25.130
2017-03-18T09:12:01.463
8000
19628
question
c++ boost
boost::spiritのセマンティックアクションで関数を使いたい


セマンティックアクション`[]`の中に、Boost.LambdaやC++ラムダ式を書く方法はどうでしょうか。

まずはBoost.Lambdaを使う例です。

[code]

    #include <iostream>
    #include <string>
    
    #include <boost/fusion/sequence/intrinsic/at_c.hpp>
    #include <boost/lambda/lambda.hpp>
    #include <boost/lambda/bind.hpp>
    #include <boost/spirit/include/qi_parse.hpp>
    #include <boost/spirit/include/qi_real.hpp>
    #include <boost/spirit/include/qi_action.hpp>
    #include <boost/spirit/include/qi_char_.hpp>
    #include <boost/spirit/include/qi_sequence.hpp>
    
    using namespace boost::spirit::qi;
    
    namespace bll = boost::lambda;
    
    double Average(const boost::fusion::vector<double, double>& x)
    {
        return (boost::fusion::at_c<0>(x) + boost::fusion::at_c<1>(x)) * 0.5;
    }
    
    int main()
    {
        std::string s = "1,2";
        auto first = s.begin();
        double val;
        bool success = parse(
            first,
            s.end(),
            (double_ >> ',' >> double_)[bll::var(val) = bll::bind(Average, bll::_1)]
        );
        if (success)
        {
            std::cout << val << std::endl;
        }
    }
    
[/code]

こっちはラムダ式を使う例です。

[code]

    #include <iostream>
    #include <string>
    
    #include <boost/fusion/sequence/intrinsic/at_c.hpp>
    #include <boost/spirit/include/qi_parse.hpp>
    #include <boost/spirit/include/qi_real.hpp>
    #include <boost/spirit/include/qi_action.hpp>
    #include <boost/spirit/include/qi_char_.hpp>
    #include <boost/spirit/include/qi_sequence.hpp>
    
    using namespace boost::spirit::qi;
    
    namespace bll = boost::lambda;
    
    double Average(const boost::fusion::vector<double, double>& x)
    {
        return (boost::fusion::at_c<0>(x) + boost::fusion::at_c<1>(x)) * 0.5;
    }
    
    int main()
    {
        std::string s = "1,2";
        auto first = s.begin();
        double val;
        bool success = parse(
            first,
            s.end(),
            (double_ >> ',' >> double_)[([&](const auto& x) {val = Average(x);})]
        );
        if (success)
        {
            std::cout << val << std::endl;
        }
    }
    
[/code]


CC BY-SA 3.0
2017-01-07T12:28:53.553
31694
2017-01-07T12:28:53.553
8616
30531
answer


[BOOST_PHOENIX_ADAPT_FUNCTION](https://www.boost.org/doc/libs/1_77_0/libs/phoenix/doc/html/phoenix/modules/function/adapting_functions.html#phoenix.modules.function.adapting_functions.boost_phoenix_adapt_function)を使えば、通常の関数をセマンティックアクション内で使える形に変換してくれるようです。

[code]

    #include <iostream>
    #include <string>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    using namespace boost::spirit::qi;
    using namespace boost::phoenix;
    
    double Average_(double d1, double d2)
    {
        return (d1 + d2) * 0.5;
    }
    
    BOOST_PHOENIX_ADAPT_FUNCTION(double, Average, Average_, 2)
    
    int main()
    {
        std::string s = "1,2";
        auto first = s.begin();
        double val;
        bool success = parse(
            first,
            s.end(),
            (double_ >> ',' >> double_)[ref(val) = Average(_1, _2)]
        );
        if (success)
        {
            std::cout << val << std::endl;
        }
    }
    
[/code]

通常の関数を変換せずにそのまま使いたい場合は、[bind](https://www.boost.org/doc/libs/1_77_0/libs/phoenix/doc/html/phoenix/modules/bind/binding_functions.html)を使えばよさそうです。

[code]

    #include <iostream>
    #include <string>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    using namespace boost::spirit::qi;
    using namespace boost::phoenix;
    
    double Average(double d1, double d2)
    {
        return (d1 + d2) * 0.5;
    }
    
    int main()
    {
        std::string s = "1,2";
        auto first = s.begin();
        double val;
        bool success = parse(
            first,
            s.end(),
            (double_ >> ',' >> double_)[ref(val) = bind(Average, _1, _2)]
        );
        if (success)
        {
            std::cout << val << std::endl;
        }
    }
    
[/code]


CC BY-SA 4.0
2021-09-27T00:36:07.820
82699
2023-03-17T21:18:17.497
2023-03-17T21:18:17.497
43506
43506
30531
answer