Semantic action evaluated in grammar constructor (or not?)

102 views Asked by At

I have been learning boost::spirit and have come across confusion that a semantic action is evaluated during grammar construction. The following code produces the output:

string=

My assumption is that this output as part of the semantic action attached to the orule.

Is there a way to avoid this behaviour? or is it something that I will need to live with if I'm using std::cout in a semantic action?

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iostream>
#include <string>

namespace phx = boost::phoenix;
namespace qi = boost::spirit::qi;

template <typename Iterator>
    struct my_grammar : qi::grammar<Iterator, std::string( )  >
{
    my_grammar() : my_grammar::base_type(orule)
    {
        using qi::_1;
        using qi::_2;
        using qi::attr;
        using qi::string;
        using phx::val;

        orule =  string("abc") [  std::cout << "string=" << _1 << std::endl ];
    }
    qi::rule< Iterator, std::string() > orule;
};

int main()
{
    typedef std::string::const_iterator iterator_type;
    typedef my_grammar<iterator_type> parser;
    parser my_parser; // Our grammar
    return 0;
}
1

There are 1 answers

1
sehe On BEST ANSWER

Short answer: No, semantic actions are not evaluated during rule initialization.

However, your problem is that you didn't (just) write a semantic action.


Yes, in your case the first part of that expression

std::cout << "string=" << _1 << std::endl

has a side-effect: (std::cout << "string=") inserts the literal into the standard ostream object and then returns std::cout by reference.

This is because you use the std::operator<< defined in the standard library, not boost::phoenix::....::operator<<¹.


You can fix this by forcing the type of the second argument to something that will select the right overload:

std::cout << phx::val("string=") << _1 << std::endl

Of course, you can always make doubly sure by doing e.g.

phx::ref(std::cout) << _1 << std::endl

But you'll notice that people tend to skip this whenever it's possible. The second operand being boost::spirit::_1 already induces expression-template context (i.e. selects the non-standard operator overloads that construct lazy actors instead of the side-effect).


¹ which likely ends up just being boost::proto::....::operator<< anyhow, but that's all delicious implementation details :)