Jason R Jason R - 1 month ago 9
C++ Question

Boost.Qi compiler error with optional parser

I'm a Boost.Qi beginner, so I'm trying some simple examples to try to get my mind around it. I'm trying to parse a string that looks like:

A:1 B:2 C:3


There is an arbitrary amount of whitespace between each component in the string. The
A:
, etc. portions are fixed, and I'd like to parse the integer values. The third component of the string,
C:3
in the above example, is optional. I came up with the following simple example to test Boost.Qi for this application:

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

namespace qi = boost::spirit::qi;

int main()
{
std::string s = "A:1 B:2 C:3";

int a, b;
boost::optional<int> c;

if (!qi::parse(s.begin(), s.end(),
qi::lit("A:") >> qi::int_ >> +qi::space >> "B:" >> qi::int_ >>
-(+qi::space >> "C:" >> qi::int_), a, b, c))
{
std::cout << "failed to parse" << std::endl;
}

std::cout << a << ' ' << b << ' ' << c.value_or(-1) << std::endl;
}


However, this fails to compile (using Boost v1.58 and g++ 5.4.0 in C++11 mode). In the typical sea of C++ template error messages, I find the following:

spirit.cc:15:55: required from here
/usr/include/boost/spirit/home/support/container.hpp:130:12: error: ‘int’ is not a class, struct, or union type
struct container_value


and

spirit.cc:15:55: required from here
/usr/include/boost/spirit/home/qi/detail/pass_container.hpp:316:66: error: no type named ‘type’ in ‘struct boost::spirit::traits::container_value<int, void>’
typedef typename traits::container_value<Attr>::type value_type;
^
/usr/include/boost/spirit/home/qi/detail/pass_container.hpp:329:15: error: no type named ‘type’ in ‘struct boost::spirit::traits::container_value<int, void>’


Is it obvious to anyone what I'm doing wrong here?

Answer

Your original grammar looks like this (with some formatting):

   qi::lit("A:")
>> qi::int_
>> +qi::space
>> qi::lit("B:")
>> qi::int_
>> -(  +qi::space
    >> qi::lit("C:")
    >> qi::int_
    )

It's important to note the attribute types each of the terminals generates:

Furthermore, note the relevant rules for compound attributes.

Based on that, you actually have:

  • integer
  • character vector (due to the +)
  • integer
  • optional (due to the -) tuple (due to the >>) of
    • character vector (due to the +)
    • integer

This does not match what you call parse with.

The key is to use the omit directive to suppress the attributes you don't care about.

The correct grammar in that case would be:

   qi::lit("A:")
>> qi::int_
>> qi::omit[+qi::space]
>> qi::lit("B:")
>> qi::int_
>> -(  qi::omit[+qi::space]
    >> qi::lit("C:")
    >> qi::int_
    )