4

I'm learning boost::spirit, and I'm trying to read and parse some text into a struct.

For example, "2: 4.6" is parsed as int 2 and double 4.6 in my TestStruct below:

#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>
#include <boost/fusion/include/std_pair.hpp>
namespace qi = boost::spirit::qi;

struct TestStruct {
  int myint;
  double mydouble;
  TestStruct() {}
  TestStruct(std::pair<int,double> p) : myint(p.first), mydouble(p.second) {}
};

template <typename Iterator, typename Skipper>
struct MyGrammar : qi::grammar<Iterator, TestStruct(), Skipper> {
  MyGrammar() : MyGrammar::base_type(mystruct) {
    mystruct0 = qi::int_ >> ":" >> qi::double_;
    mystruct = mystruct0;
  }
  qi::rule<Iterator, std::pair<int,double>(), Skipper> mystruct0;
  qi::rule<Iterator, TestStruct(), Skipper> mystruct;
};

int main() {
  typedef boost::spirit::istream_iterator It;
  std::cin.unsetf(std::ios::skipws);
  It it(std::cin), end; // input example: "2: 3.4"                                                                              

  MyGrammar<It, qi::space_type> gr;
  TestStruct ts;
  if (qi::phrase_parse(it, end, gr, qi::space, ts) && it == end)
    std::cout << ts.myint << ", " << ts.mydouble << std::endl;
  return 0;
}

It works nicely, but I'm wondering how this code can be simplified?

For example, I would like to get rid of the mystruct0 grammar rule, which is only there to mark the type std::pair<int,double>, which is then used to automatically construct the TestStruct object from the mystruct rule.

I'd also like to be able to get rid of the TestStruct constructor from std::pair, if possible.

So, can the following code somehow made to be compiled? That would be a much nicer solution:

struct TestStruct {
  int myint;
  double mydouble;
  TestStruct() {}
  TestStruct(int i, double d) : myint(i), mydouble(d) {}
};

template <typename Iterator, typename Skipper>
struct MyGrammar : qi::grammar<Iterator, TestStruct(), Skipper> {
  MyGrammar() : MyGrammar::base_type(mystruct) {
    mystruct = qi::int_ >> ":" >> qi::double_;
  }
  qi::rule<Iterator, TestStruct(), Skipper> mystruct;
};

int main() {
  typedef boost::spirit::istream_iterator It;
  std::cin.unsetf(std::ios::skipws);
  It it(std::cin), end; // input example: "2: 3.4"                                                                              

  MyGrammar<It, qi::space_type> gr;
  TestStruct ts;
  if (qi::phrase_parse(it, end, gr, qi::space, ts) && it == end)
    std::cout << ts.myint << ", " << ts.mydouble << std::endl;
  return 0;
}

Unfortunately, the compiler says:

boost_1_49_0/include/boost/spirit/home/qi/detail/assign_to.hpp:123: 
error: no matching function for call to ‘TestStruct::TestStruct(const int&)’

2 Answers 2

3

To be able to parse values "sequentially" into a struct, you need to transform it into a fusion tuple, as described here.

In your case, this means you need to

  1. Include the necessary header

    #include <boost/fusion/adapted/struct/adapt_struct.hpp>
    
  2. Use the fusion-adapt struct macro. Best place it right after the declaration of TestStruct:

    BOOST_FUSION_ADAPT_STRUCT(
            TestStruct,
            (int,myint)
            (double,mydouble)
         )
    

With these two changes, your simplified version compiles and yields the desired results. Not sure if it is really much simpler now – but if you plan to add further members to your struct, it's a good starting point as it may help simplify things in the future.

I don't see any other major changes you could make to make the program simpler.

Sign up to request clarification or add additional context in comments.

Comments

2

Yes that code can be compiled. In fact, you can do without the constructors: the default (compiler generated) constructor is fine.

All you need to do is adapt your struct as a fusion sequence. (As a bonus, this works for karma as well.)
This is precisely the magic that made std::pair work in the first place.

#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted/struct.hpp>
namespace qi = boost::spirit::qi;

struct TestStruct {
    int myint;
    double mydouble;
};

BOOST_FUSION_ADAPT_STRUCT(TestStruct, (int, myint)(double, mydouble));

template <typename Iterator, typename Skipper>
struct MyGrammar : qi::grammar<Iterator, TestStruct(), Skipper> {
    MyGrammar() : MyGrammar::base_type(mystruct) {
        mystruct = qi::int_ >> ":" >> qi::double_;
    }
    qi::rule<Iterator, TestStruct(), Skipper> mystruct;
};

int main() {
    typedef std::string::const_iterator It;
    const std::string input("2: 3.4");
    It it(input.begin()), end(input.end());

    MyGrammar<It, qi::space_type> gr;
    TestStruct ts;

    if (qi::phrase_parse(it, end, gr, qi::space, ts) && it == end)
        std::cout << ts.myint << ", " << ts.mydouble << std::endl;

    return 0;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.