basic boost spirit semantic action doesn't compile

淺唱寂寞╮ 提交于 2019-12-04 16:58:07

TL;DR use

template <typename, typename> struct result { typedef expression_ast type; };

inside binary_expr struct. Here's why:


You declare a functor object, to be used as a Phoenix lazy actor.

The functor is what's known as a Deferred/Polymorphic Calleable Object (PCE) in the Boost documentation. This means that the expression templates building the Phoenix actors will actually only do actual overload resolution/type deduction for the function arguments at the actual time of application.

The function return type cannot be deduced (just like it cannot in regular (non-lazy) C++). This is where boost libraries employ the BOOST_RESULT_OF protocol.

Note The RESULT_OF protocol is obsolete/redundant when using C++11, since C++11 has decltype. To enable that, on most compilers you need to

#define BOOST_RESULT_OF_USE_DECLTPYE

(allthough the release notes of v1_52_0 make it clear that decltype is becoming the default for compilers that support it well enough).

What this protocol entails, is this:

  • When you use Polymorphic Calleable Object with n parameters, boost will look for a nested type typedef inside a nested class template result, parameterized with the n actual argument types. This will be the return type
  • The actual functor invocation calls operator() with those arguments. Here, C++ will be left to do the overload resolution.

In general, you are expected to write your functor in fully parameterized style:

template<char OP>
struct binary_expr
{
    template <typename, typename> struct result { typedef expression_ast type; };

    template <typename A, typename B>
    typename result<A,B>::type operator()(A const&a,B const&b) const {
        return expression_ast(binary_op( OP, a, b ));
    }
};

This works. However, because the actual overload resolution on argument types is done by the compiler, you can change the operator() signature to use fixed types:

template <typename A, typename B>
expression_ast operator()(A const&a,B const&b) const {
    return binary_op(OP, a, b);
}

If you wish, you can do away with (some) template parameters:

template <typename E>
expression_ast operator()(E const&a,E const&b) const {
    return binary_op(OP, a, b);
}

Or even not a function template at all:

expression_ast operator()(expression_ast const&,expression_ast const&) const;

Everything is fine, as long as one overload matches the actual argument types passed. The important bit, though, is that the result<...>::type is getting evaluated regardless of the signature of operator(), and as such it will need precisely the number of arguments expected.

Note also that you can, in this fashion, combine functors of different arity:

template <char OP> struct operator_expr 
{
    template <typename T, typename=T> struct result 
        { typedef expression_ast type; };

    expression_ast operator()(expression_ast const& expr) const
    { return expression_ast(unary_op(OP, expr)); }

    expression_ast operator()(expression_ast const&a, expression_ast const&b) const 
    { return binary_op(OP, a, b); }
};

static boost::phoenix::function<operator_expr<'-'>> neg;
static boost::phoenix::function<operator_expr<'>'>> gt;

This works, because both result<T>::type and result<T,U>::type are valid type expressions with this definition.


Bonus notes:

  1. You had a strangeness there where you said

    template <typename T> struct result { typedef T type; };
    

    instead of

    template <typename> struct result { typedef expression_ast type; };
    

    This is because the return type shouls, in fact, not vary depending on the actual argument type. In your sample, the argument type would normally be the same, but it didn't make sense technically.

  2. You can do things without the BOOST_RESULT_OF protocol if you use decltype. This means you can just drop the nested result struct

  3. You can also adapt normal functions as Phoenix actors:

    #define BOOST_SPIRIT_USE_PHOENIX_V3
    
    // ...
    
    expression_ast neg_expr(expression_ast const&a)                         { return unary_op ('-', a); }
    expression_ast gt_expr (expression_ast const&a, expression_ast const&b) { return binary_op('>', a, b); }
    
    BOOST_PHOENIX_ADAPT_FUNCTION(expression_ast, neg, neg_expr, 1)
    BOOST_PHOENIX_ADAPT_FUNCTION(expression_ast, gt,  gt_expr,  2)
    

    This will basically write the functor objects for you, including the RESULT_OF protocol bits.

  4. Finally, you can use standard Phoenix actors instead of defining custom ones. In that case, there is no need for any of the above:

    using phx = boost::phoenix;
    // ...
       |   ('>' >> factor [_val = phx::construct<binary_op>('>', _val, _1)]) // PROBLEM HERE!
    // ...
       |   ('-' >> factor [_val = phx::construct<unary_op>('-', _1)])
    

Wrap-up

The complete code is here: http://ideone.com/Xv9IH1 and was tested on

  • MSVC 2012, boost 1_52_0, Win64
  • GCC 4.8, boost 1_52_0, Win64
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!