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&)’
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;
}