3

I'm trying to pass semantic action in a grammar's inherited argument.

In the very basic example below the grammar parses two numbers and I pass semantic action (in a form of c++ lambda) into it and I'd like this action to be called on parsing of the first number. However it does not called but silently ignored and I'd like to know why is it so and what is the proper way to do such things.

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

using namespace std;
using namespace boost;

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

template <typename Iterator, typename Action>
struct two_numbers : qi::grammar<Iterator, void (Action const&)>
{
  two_numbers() : two_numbers::base_type(start)
  {
    using namespace qi;
    start = int_ [ _r1 ] >> ' ' >> int_;
  }
  qi::rule<Iterator, void (Action const&)> start;
};

int main ()
{
  string input { "42 21" };
  auto first=std::begin (input), last=std::end(input);

  static const auto my_action = [] (auto&& p) {
    cout << "the meaning of life is " << p << "\n";
  };

  static const two_numbers <decltype(first), decltype (my_action)> p;

  if (qi::parse (first, last, p(phx::ref(my_action))))
    cout << "parse ok\n";
}

The expected output is:

the meaning of life is 42
parse ok

And the real output is:

parse ok
Nikki Chumakov
  • 1,215
  • 8
  • 18
  • Obligatory link: [Boost Spirit: “Semantic actions are evil”?](http://stackoverflow.com/questions/8259440/boost-spirit-semantic-actions-are-evil) – sehe Oct 16 '14 at 22:28

1 Answers1

4

First, immediate, response:

"I'm trying to pass semantic action in a grammar's inherited argument."

instant traumatic shock. You... you... what?!

C++ is not very good for higher order programming, certainly not with static polymorphism based on expression templates. In fact it is, but in my previous answer I already cautioned against UB when storing expression templates in named objects (≅ variables).

That time around it was UB that you spotted. That's lucky, in my opinion.

Recently, I've already encountered another question about similar goals:

Pay special attention to the comment thread. I don't think this is a sane path, at least not until Boost Mpl has been done with full C++11 goodness (Boost Hana, perhaps?) and Proto-0x is released.

By then, Spirit X3 is probably mature, and we're just left with the gap of Boost Phoenix. I'm not sure whether that's on anyones agenda.

In short: we'll be stuck in this "halfway" land where we can have nice things, but with some pretty constrained restrictions. We should probably avoid getting carried away and pretending we're suddenly capable of writing Haskell in C++.

Also relevant: There's a proposal (N4221, pdf) for Generalized Lifetime Extension of References in C++. It comes with some good examples of simple applications of say Boost Range adaptors that are silently UB in current C++. E.g. from §2.3 Universal observation:

std::vector<int> vec;
for (int val : vec | boost::adaptors::reversed
                   | boost::adaptors::uniqued) 
{
       // Error: result of (vec | boost::adaptors::reversed) died.
}

Solution

That said, since the inherited argument would be a functor (not a lazy actor), you need to bind it:

    start = int_ [ phx::bind(phx::cref(_r1), qi::_1) ] >> ' ' >> int_;

That does work: Live On Coliru

However, I don't recommend this

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Added a link to the N4221 proposal (thanks @AndyProwl) – sehe Oct 16 '14 at 22:40
  • Thank you for the answer. I modified my example and now pass lazy actor, but it is still not working as expected: [COLIRU Link](http://coliru.stacked-crooked.com/a/7790ff05a15f234c) Also, if the undefined behavior happens because of early destroyed prvalue and the dangling reference, can you show what is the problem reference exactly in the code? – Nikki Chumakov Oct 17 '14 at 06:26
  • @NikkiChumakov I cannot (at the moment). And I probably won't find the motivation. There **is** something in Boost Phoenix, though, that tells whether a Phoenix expression template is "stateless" (maybe I half-remember this being the case if the type of the expression is default-constructible?). If that's the case, then there is no issue storing it. Otherwise, all bets are off. There is a post by Eric Niebler himself detailing this, IIRC. – sehe Oct 17 '14 at 06:35
  • This might be the post I remembered: **[Static functions from boost.lambda or boost.phoenix](http://stackoverflow.com/a/12326204/85371)**. The goal there was slightly different, so you'll have to see through that to see how it all applies here. – sehe Oct 17 '14 at 06:38
  • Sorry, I did not want to give you an exercise of finding problematic places :) I was just... too surprised. And please note I'm not native english speaking, so there is some room for miscommunication because of that. I was surprised because nothing in my code looks wrong - I just create static object (lambda) which lifetime is very well defined, and pass the const reference to it into the phoenix ref function and into spirit grammar. And nothing in Phoenix/Spirit specs make me think this is prohibit. – Nikki Chumakov Oct 17 '14 at 07:58
  • 1
    But what is the difference between my static lambda reference than and any other objects I may want to pass in grammar's inherited attributes then? And what is really responsible for UB here - my code? Phoenix? Proto? C++? And yes, the situation is very similar to range::adaptors, when nothing in specs make us think that is it impossible to combine several adaptors in a pipeline. Anyway, thank you for interesting reading list like Boost.Hana and proposal and other posts you are referring. Really interesting. – Nikki Chumakov Oct 17 '14 at 08:07
  • @NikkiChumakov To be fair, I don't see something wrong in the immediate solution I posted. But, it's a very slippery slope, and worst of all, there will be **absolutely no warning** from the compiler, and often not even from valgrind when things go sour. I remember running into [a bug with karma from just using `BOOST_FUSION_ADAPT_ADT`. The attribute handling code subtly leads to dangling refs into member the access proxies](https://svn.boost.org/trac/boost/ticket/6126#comment:13). This is why I spend quite some time warning about things. – sehe Oct 17 '14 at 08:12
  • I just want people to be aware of the limits and pitfalls involved when doing higher-order combination of TMP-heavy libraries in general, and using `auto` with expression templates in particular. (As @LucDanton pointed out, TMP is a bit too broad here). – sehe Oct 17 '14 at 08:12
  • For context: Boost Phoenix has this tagline: _"Define small unnamed function objects at the actual call site, and more."_ IOW: if it persists for longer than just the call sites, you’re in trouble. – sehe Oct 17 '14 at 08:17