Spirit Qi Attribute Propagation Issue with Single-Member Struct

Spirit Qi attribute propagation issue with single-member struct

This is a quite infamous edge-case in Spirit. The problem is, special-case handling of single-element Fusion Sequences in Spirit breaks some abstractions.

The usual workaround is to adapt the exposed-attribute side to be less-trivial:

rule<It, single_member_struct()> r = eps >> XXX; 
// the `eps` is there to break the spell

However, here that won't work, because your (a > XXX > b) subexpression results in another vector1<decltype(member_type)> and this time, no amount of smart parenthesizing or eps-ing will save you.[1]

To cut the long story short, I have three workarounds:


1. #define KEEP_STRING_WORKAROUND

See it Live On Coliru

In which you'll simply allow gr_identifier to return a std::wstring[2]:

rule<It, std::string()> gr_identifier = 
(alpha | '_') >> *(alnum | '_');

This effectively just postpones the magic attribute transformation that uses the Fusion adaptation if identifier, and thereby breaks the spell:

rule<It, problem(), qi::space_type> gr_problem = 
gr_identifier
>> gr_identifier
>> ('(' > gr_identifier > ')')
;

Just works. I think this is probably the least intrusive workaround


2. #define DUMMY_WORKAROUND

See it Live On Coliru

Whereby you unjinx the magix by ... making the identifier struct not fusion adapted to a single-element fusion sequence. Yes. This involves the EvilHack™ of adding a dummy field. To minimize confusion, lets' make it qi::unused_type though:

struct identifier
{
std::string name;
qi::unused_type dummy;
};

BOOST_FUSION_ADAPT_STRUCT(
identifier,
(std::string, name)
(qi::unused_type, dummy)
)

And now:

rule<It, identifier()> gr_identifier = 
(alpha | '_') >> *(alnum | '_') >> attr(42); // that's hacky

Works


3. #define NO_ADAPT_WORKAROUND

See it Live On Coliru

The final workaround might be the most obvious: don't adapt the struct as a fusion sequence in the first place, and profit:

struct identifier
{
std::string name;

identifier() = default;

explicit identifier(std::string name)
: name(std::move(name))
{}
};

Note that to allow attribute propagation, now you will need suitable conversion constructors to be present. Also, the default constructor is required for exposed attributes in Spirit.

Now,

rule<It, identifier()> gr_identifier = 
as_string [ (alpha | '_') >> *(alnum | '_') ]; // cleaner... but no fusion

works. This might be more intuitive if you didn't need Fusion of the type for other purposes.

Note: this variant could well be the most efficient in compile-time

Summary

I think for your code, there are 2 perfectly viable workarounds (#1 and #3), and one less-than-stellar (the one with the dummy field), but I included it for documentary purposes.

Full Code

For future reference

#define BOOST_SPIRIT_DEBUG
#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

namespace qi = boost::spirit::qi;

//////////////////////////////////////////
// Select workaround to demonstrate
#define KEEP_STRING_WORKAROUND
// #define DUMMY_WORKAROUND™
// #define NO_ADAPT_WORKAROUND
//////////////////////////////////////////

#if defined(KEEP_STRING_WORKAROUND)
struct identifier
{
std::string name;
};

BOOST_FUSION_ADAPT_STRUCT(
identifier,
(std::string, name)
)
#elif defined(DUMMY_WORKAROUND)
struct identifier
{
std::string name;
qi::unused_type dummy;
};

BOOST_FUSION_ADAPT_STRUCT(
identifier,
(std::string, name)
(qi::unused_type, dummy)
)
#elif defined(NO_ADAPT_WORKAROUND)
struct identifier
{
std::string name;

identifier() = default;

explicit identifier(std::string name)
: name(std::move(name))
{}
};
#endif

struct problem
{
identifier _1;
identifier _2;
identifier _3;
};

BOOST_FUSION_ADAPT_STRUCT(
problem,
(identifier, _1)
(identifier, _2)
(identifier, _3)
)

//////////////////////////////////////////
// For BOOST_SPIRIT_DEBUG only:
static inline std::ostream& operator<<(std::ostream& os, identifier const& id) {
return os << id.name;
}
//////////////////////////////////////////

int main()
{
using namespace qi;
typedef std::string::const_iterator It;
#if defined(KEEP_STRING_WORKAROUND)
rule<It, std::string()> gr_identifier =
(alpha | '_') >> *(alnum | '_');
#elif defined(DUMMY_WORKAROUND)
rule<It, identifier()> gr_identifier =
(alpha | '_') >> *(alnum | '_') >> attr(42); // that's hacky
#elif defined(NO_ADAPT_WORKAROUND)
rule<It, identifier()> gr_identifier =
as_string [ (alpha | '_') >> *(alnum | '_') ]; // cleaner... but no fusion
#endif

rule<It, problem(), qi::space_type> gr_problem =
gr_identifier
>> gr_identifier
>> ('(' > gr_identifier > ')')
;

std::string input = "foo goo(hoo)";

BOOST_SPIRIT_DEBUG_NODES((gr_problem)(gr_identifier));

It f(begin(input)), l(end(input));
bool dummy = phrase_parse(f, l, gr_problem, qi::space);

return dummy? 0 : 255;
}

[1] Believe me, I tried, even while inserting qi::unused_type "fake" attributes, and/or using attr_cast<> or helper rules to coerce the type of the subexpression.

[2] For demo purposes, I used std::string because I believe it mixes better with BOOST_SPIRIT_DEBUG

Parsing variant of struct with a single member using Boost Spirit X3 and Fusion

I hate to say, but this is a well-known limitation and it keeps cropping up. I've kind of given up on trying to get it fixed. It's an edge case that is usually easy to work-around or avoid.

See e.g.

  • boost::spirit::x3 attribute compatibility rules, intuition or code?
  • How to move results parsed results into a struct using spirit x3
  • https://github.com/boostorg/spirit/pull/178

A longer list exists for Qi.

In your cause I would probably avoid parsing into TestStruct and rather parse into float. Otherwise, parse it and propagate using a semantic action instead of automatic attribute propagation.

I tried and found it pretty hard to get over the hurdle in this specific case (it appears to be the "inverse" problem, where an already correctly matched rule exposing TestResult is still posing a problem down the road. Clearly the attribute synthesis rules are wrong again).

Brute Force

auto assign = [](auto& ctx) { _val(ctx) = _attr(ctx); };

Doing the attribute propagation manually:

auto test_struct
= x3::rule<struct test_struct, TestStruct>{}
= x3::float_ >> ",";

auto test_variant
= x3::rule<struct test_variant, TestVariant>{}
= test_struct [assign] | "default" >> x3::attr("default"s)[assign];

Works: Live On Coliru

Indeed, the "official" propagation would look more like¹

auto assign = [](auto& ctx) {
x3::traits::move_to(_attr(ctx), _val(ctx));
};

And it appears to not-work: Live On Coliru


¹ but not exactly, there is a lot of meta-programming conditions before that

boost::spirit::qi Expectation Parser and parser grouping unexpected behaviour

  1. Is it worth preventing back-tracking?

    Absolutely. Preventing back tracking in general is a proven way to improve parser performance.

    • reduce the use of (negative) lookahead (operator !, operator - and some operator &)
    • order branches (operator |, operator ||, operator^ and some operator */-/+) such that the most frequent/likely branch is ordered first, or that the most costly branch is tried last

    Using expectation points (>) does not essentially reduce backtracking: it will just disallow it. This will enable targeted error messages, prevent useless 'parsing-into-the-unknown'.

  2. Why do I need the grouping operator ()

    I'm not sure. I had a check using my what_is_the_attr helpers from here

    • ident >> op >> repeat(1,3)[any] >> "->" >> any
      synthesizes attribute:

      fusion::vector4<string, string, vector<string>, string>
    • ident >> op > repeat(1,3)[any] > "->" > any
      synthesizes attribute:

      fusion::vector3<fusion::vector2<string, string>, vector<string>, string>

    I haven't found the need to group subexpressions using parentheses (things compile), but obviously DataT needs to be modified to match the changed layout.

    typedef boost::tuple<
    boost::tuple<std::string, std::string>,
    std::vector<std::string>,
    std::string
    > DataT;

The full code below shows how I'd prefer to do that, using adapted structs.

  1. Does my above example really stop back-tracking after operationRule is matched (I suspect not, it seems that if the entire parser inside the (...) fails backtracking will be allowed)?

    Absolutely. If the expectation(s) is not met, a qi::expectation_failure<> exception is thrown. This by default aborts the parse. You could use qi::on_error to retry, fail, accept or rethrow. The MiniXML example has very good examples on using expectation points with qi::on_error

  2. If the answer to the previous question is /no/, how do I construct a rule that allows backtracking if operation is /not/ matched, but does not allow backtracking once operation /is/ matched?

  3. Why does the grouping operator destroy the attribute grammar - requiring actions?

    It doesn't destroy the attribute grammar, it just changes the exposed type. So, if you bind an appropriate attribute reference to the rule/grammar, you won't need semantic actions. Now, I feel there should be ways to go without the grouping , so let me try it (preferrably on your short selfcontained sample). And indeed I have found no such need. I've added a full example to help you see what is happening in my testing, and not using semantic actions.

Full Code

The full code show 5 scenarios:

  • OPTION 1: Original without expectations

    (no relevant changes)

  • OPTION 2: with expectations

    Using the modified typedef for DataT (as shown above)

  • OPTION 3: adapted struct, without expectations

    Using a userdefined struct with BOOST_FUSION_ADAPT_STRUCT

  • OPTION 4: adapted struct, with expectations

    Modifying the adapted struct from OPTION 3

  • OPTION 5: lookahead hack

    This one leverages a 'clever' (?) hack, by making all >> into expectations, and detecting the presence of a operationRule-match beforehand. This is of course suboptimal, but allows you to keep DataT unmodified, and without using semantic actions.

Obviously, define OPTION to the desired value before compiling.

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/adapted.hpp>
#include <iostream>

namespace qi = boost::spirit::qi;
namespace karma = boost::spirit::karma;

#ifndef OPTION
#define OPTION 5
#endif

#if OPTION == 1 || OPTION == 5 // original without expectations (OR lookahead hack)
typedef boost::tuple<std::string, std::string, std::vector<std::string>, std::string> DataT;
#elif OPTION == 2 // with expectations
typedef boost::tuple<boost::tuple<std::string, std::string>, std::vector<std::string>, std::string> DataT;
#elif OPTION == 3 // adapted struct, without expectations
struct DataT
{
std::string identifier, operation;
std::vector<std::string> values;
std::string destination;
};

BOOST_FUSION_ADAPT_STRUCT(DataT, (std::string, identifier)(std::string, operation)(std::vector<std::string>, values)(std::string, destination));
#elif OPTION == 4 // adapted struct, with expectations
struct IdOpT
{
std::string identifier, operation;
};
struct DataT
{
IdOpT idop;
std::vector<std::string> values;
std::string destination;
};

BOOST_FUSION_ADAPT_STRUCT(IdOpT, (std::string, identifier)(std::string, operation));
BOOST_FUSION_ADAPT_STRUCT(DataT, (IdOpT, idop)(std::vector<std::string>, values)(std::string, destination));
#endif

template <typename Iterator>
struct test_parser : qi::grammar<Iterator, DataT(), qi::space_type, qi::locals<char> >
{
test_parser() : test_parser::base_type(test, "test")
{
using namespace qi;

quoted_string =
omit [ char_("'\"") [_a =_1] ]
>> no_skip [ *(char_ - char_(_a)) ]
> lit(_a);

any_string = quoted_string | +qi::alnum;

identifier = lexeme [ alnum >> *graph ];

operationRule = string("add") | "sub";
arrow = "->";

#if OPTION == 1 || OPTION == 3 // without expectations
test = identifier >> operationRule >> repeat(1,3)[any_string] >> arrow >> any_string;
#elif OPTION == 2 || OPTION == 4 // with expectations
test = identifier >> operationRule > repeat(1,3)[any_string] > arrow > any_string;
#elif OPTION == 5 // lookahead hack
test = &(identifier >> operationRule) > identifier > operationRule > repeat(1,3)[any_string] > arrow > any_string;
#endif
}

qi::rule<Iterator, qi::space_type/*, qi::locals<char> */> arrow;
qi::rule<Iterator, std::string(), qi::space_type/*, qi::locals<char> */> operationRule;
qi::rule<Iterator, std::string(), qi::space_type/*, qi::locals<char> */> identifier;
qi::rule<Iterator, std::string(), qi::space_type, qi::locals<char> > quoted_string, any_string;
qi::rule<Iterator, DataT(), qi::space_type, qi::locals<char> > test;
};

int main()
{
std::string str("addx001 add 'str1' \"str2\" -> \"str3\"");
test_parser<std::string::const_iterator> grammar;
std::string::const_iterator iter = str.begin();
std::string::const_iterator end = str.end();

DataT data;
bool r = phrase_parse(iter, end, grammar, qi::space, data);

if (r)
{
using namespace karma;
std::cout << "OPTION " << OPTION << ": " << str << " --> ";
#if OPTION == 1 || OPTION == 3 || OPTION == 5 // without expectations (OR lookahead hack)
std::cout << format(delimit[auto_ << auto_ << '[' << auto_ << ']' << " --> " << auto_], data) << "\n";
#elif OPTION == 2 || OPTION == 4 // with expectations
std::cout << format(delimit[auto_ << '[' << auto_ << ']' << " --> " << auto_], data) << "\n";
#endif
}
if (iter!=end)
std::cout << "Remaining: " << std::string(iter,end) << "\n";
}

Output for all OPTIONS:

for a in 1 2 3 4 5; do g++ -DOPTION=$a -I ~/custom/boost/ test.cpp -o test$a && ./test$a; done
OPTION 1: addx001 add 'str1' "str2" -> "str3" --> addx001 add [ str1 str2 ] --> str3
OPTION 2: addx001 add 'str1' "str2" -> "str3" --> addx001 add [ str1 str2 ] --> str3
OPTION 3: addx001 add 'str1' "str2" -> "str3" --> addx001 add [ str1 str2 ] --> str3
OPTION 4: addx001 add 'str1' "str2" -> "str3" --> addx001 add [ str1 str2 ] --> str3
OPTION 5: addx001 add 'str1' "str2" -> "str3" --> addx001 add [ str1 str2 ] --> str3

boost::spirit parsing into struct with std::array

You are going places :) You seem to have reached warp speed with Spirit in no time.

EDITED After reading the question closer I noticed that you ask for a shorter, less intrusive way.

I don't know a non-intrusive way to fix it, but you can use boost::array if you specialize is_container for it.

That's pretty unintrusive, but still changes your types.

Live On Coliru

#include <boost/fusion/include/tuple.hpp>
#include <boost/fusion/adapted/boost_array.hpp>
#include <boost/spirit/include/qi.hpp>
#include <string>

namespace boost { namespace spirit { namespace traits {
template <typename T, size_t N>
struct is_container<boost::array<T, N>, void> : mpl::false_ { };
} } }

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;

struct StructWithArray
{
double dummy_; // see http://stackoverflow.com/questions/19823413/spirit-qi-attribute-propagation-issue-with-single-member-struct
boost::array<double, 6> ary_;
};

BOOST_FUSION_ADAPT_STRUCT(StructWithArray, dummy_, ary_)

template <typename Iterator, typename Skipper>
struct StructWithArrayParser
: qi::grammar<Iterator, StructWithArray(), Skipper>
{
StructWithArrayParser() : StructWithArrayParser::base_type(start)
{
using qi::double_;

arrayLine = double_ > double_ > double_ > double_ > double_ > double_;
start = double_ > arrayLine;
}

private:
qi::rule<Iterator, boost::array<double, 6>(), Skipper> arrayLine;
qi::rule<Iterator, StructWithArray(), Skipper> start;
};

int main() {
std::string arrayStr = "0 1 2 3 4 5 6";
using It = std::string::const_iterator;
It it = arrayStr.begin(), endIt = arrayStr.end();
StructWithArrayParser<It, ascii::space_type> grammar;

StructWithArray structWithArray;
bool ret = phrase_parse(it, endIt, grammar, ascii::space, structWithArray);
std::cout << std::boolalpha << ret << "\n";

for (double v : structWithArray.ary_)
std::cout << v << " ";
}

Prints:

true
1 2 3 4 5 6

boost::spirit::qi compiler error: cannot convert 'const some_stuff::return_type' to 'unsigned int' without a conversion operator

You've been bit by the infamous "single-element sequence" bug/limitation: Spirit Qi attribute propagation issue with single-member struct

Adding a dummy field to return_type make it work (also added debugging):

Live On Coliru

#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iostream>
#include <string>

using namespace boost::spirit;

struct some_stuff {
struct return_type {
unsigned int field_1;
bool dummy;
};

struct id : qi::grammar<std::string::iterator> {
id() : id::base_type(rule_) {
rule_ = qi::lit("?AL");
BOOST_SPIRIT_DEBUG_NODES((rule_))
}

private:
qi::rule<std::string::iterator> rule_;
};

struct payload : qi::grammar<std::string::iterator, return_type()> {
payload() : payload::base_type{ rule_ } {
rule_ = qi::uint_ >> qi::attr(false);
BOOST_SPIRIT_DEBUG_NODES((rule_))
}

private:
qi::rule<std::string::iterator, return_type()> rule_;
};
};
BOOST_FUSION_ADAPT_STRUCT(some_stuff::return_type, field_1, dummy)

struct my_grammar : qi::grammar<std::string::iterator, some_stuff::return_type()> {
my_grammar() : my_grammar::base_type(rule_) {
rule_ = "+++AT" >> i_ >> ':' >> qi::omit[qi::uint_] >> ':' >> p_;
BOOST_SPIRIT_DEBUG_NODES((rule_))
}

private:
some_stuff::id i_;
some_stuff::payload p_;
qi::rule<std::string::iterator, some_stuff::return_type()> rule_;
};

int main() {
std::string s("+++AT?AL:1:3,5");
my_grammar g_;
some_stuff::return_type v_;

auto it = s.begin();
if (qi::phrase_parse(it, s.end(), g_, ascii::space, v_)) {
std::cout << "Field 1: " << v_.field_1 << std::endl;
}
}

Prints

<rule_>
<try>+++AT?AL:1:3,5</try>
<rule_>
<try>?AL:1:3,5</try>
<success>:1:3,5</success>
<attributes>[]</attributes>
</rule_>
<rule_>
<try>3,5</try>
<success>,5</success>
<attributes>[[3, 0]]</attributes>
</rule_>
<success>,5</success>
<attributes>[[3, 0]]</attributes>
</rule_>
Field 1: 3

NOTE

You're using phrase_parse without a skipper. That's not working, and not what you want: Boost spirit skipper issues

Compiler error when adapting struct with BOOST_FUSION_ADAPT_STRUCT

You need to emit an unused type in your rule:

qi::rule<std::string::iterator, VectorWrapper(), qi::space_type > testRule = 
+(qi::string("aa")) >> qi::attr(false);

Read empty values with boost::spirit

You just want to make sure you parse a value for "empty" strings too.

value = +(char_ - ',' - eol) | attr("(unspecified)");
entry = value >> ',' >> value >> ',' >> value >> eol;

See the demo:

Live On Coliru

//#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

struct data {
std::string a;
std::string b;
std::string c;
};

BOOST_FUSION_ADAPT_STRUCT(data, (std::string, a)(std::string, b)(std::string, c))

template <typename Iterator, typename skipper = qi::blank_type>
struct google_parser : qi::grammar<Iterator, data(), skipper> {
google_parser() : google_parser::base_type(entry, "contacts") {
using namespace qi;

value = +(char_ - ',' - eol) | attr("(unspecified)");
entry = value >> ',' >> value >> ',' >> value >> eol;

BOOST_SPIRIT_DEBUG_NODES((value)(entry))
}
private:
qi::rule<Iterator, std::string()> value;
qi::rule<Iterator, data(), skipper> entry;
};

int main() {
using It = std::string::const_iterator;
google_parser<It> p;

for (std::string input : {
"something, awful, is\n",
"fine,,just\n",
"like something missing: ,,\n",
})
{
It f = input.begin(), l = input.end();

data parsed;
bool ok = qi::phrase_parse(f,l,p,qi::blank,parsed);

if (ok)
std::cout << "Parsed: '" << parsed.a << "', '" << parsed.b << "', '" << parsed.c << "'\n";
else
std::cout << "Parse failed\n";

if (f!=l)
std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
}
}

Prints:

Parsed: 'something', 'awful', 'is'
Parsed: 'fine', '(unspecified)', 'just'
Parsed: 'like something missing: ', '(unspecified)', '(unspecified)'

However, you have a bigger problem. The assumption that qi::repeat(2) [ value ] will parse into 2 strings doesn't work.

repeat, like operator*, operator+ and operator% parse into a container attribute. In this case the container attribute (string) will receive the input from the second value as well:

Live On Coliru

Parsed: 'somethingawful', 'is', ''
Parsed: 'fine(unspecified)', 'just', ''
Parsed: 'like something missing: (unspecified)', '(unspecified)', ''

Since this is not what you want, reconsider your data types:

  • either don't adapt the struct but instead write a customization trait to assign the fields (see http://www.boost.org/doc/libs/1_57_0/libs/spirit/doc/html/spirit/advanced/customize.html)

  • change the struct to contain a vector of std::string to match the exposed attributes

  • or create an auto-parser generator:

The auto_ approach:

If you teach Qi how to extract a single value, you can use a simple rule like

entry = skip(skipper() | ',') [auto_] >> eol;

This way, Spirit itself will generate the correct number of value extractions for the given Fusion sequence!

Here's a quick an dirty approach:

CAVEAT Specializing for std::string directly like this might not be the best idea (it might not always be appropriate and might interact badly with other parsers). However, by default create_parser<std::string> is not defined (because, what would it do?) so I seized the opportunity for the purpose of this demonstration:

namespace boost { namespace spirit { namespace traits {
template <> struct create_parser<std::string> {
typedef proto::result_of::deep_copy<
BOOST_TYPEOF(
qi::lexeme [+(qi::char_ - ',' - qi::eol)] | qi::attr("(unspecified)")
)
>::type type;

static type call() {
return proto::deep_copy(
qi::lexeme [+(qi::char_ - ',' - qi::eol)] | qi::attr("(unspecified)")
);
}
};
}}}

Again, see the demo output:

Live On Coliru

Parsed: 'something', 'awful', 'is'
Parsed: 'fine', 'just', '(unspecified)'
Parsed: 'like something missing: ', '(unspecified)', '(unspecified)'

NOTE There was some advanced sorcery to get the skipper to work "just right" (see skip()[] and lexeme[]). Some general explanations can be found here: Boost spirit skipper issues

UPDATE

The Container Approach

There's a subtlety to that. Two actually. So here's a demo:

Live On Coliru

//#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

struct data {
std::vector<std::string> parts;
};

BOOST_FUSION_ADAPT_STRUCT(data, (std::vector<std::string>, parts))

template <typename Iterator, typename skipper = qi::blank_type>
struct google_parser : qi::grammar<Iterator, data(), skipper> {
google_parser() : google_parser::base_type(entry, "contacts") {
using namespace qi;
qi::as<std::vector<std::string> > strings;

value = +(char_ - ',' - eol) | attr("(unspecified)");
entry = strings [ repeat(2) [ value >> ',' ] >> value ] >> eol;

BOOST_SPIRIT_DEBUG_NODES((value)(entry))
}
private:
qi::rule<Iterator, std::string()> value;
qi::rule<Iterator, data(), skipper> entry;
};

int main() {
using It = std::string::const_iterator;
google_parser<It> p;

for (std::string input : {
"something, awful, is\n",
"fine,,just\n",
"like something missing: ,,\n",
})
{
It f = input.begin(), l = input.end();

data parsed;
bool ok = qi::phrase_parse(f,l,p,qi::blank,parsed);

if (ok) {
std::cout << "Parsed: ";
for (auto& part : parsed.parts)
std::cout << "'" << part << "' ";
std::cout << "\n";
}
else
std::cout << "Parse failed\n";

if (f!=l)
std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
}
}

The subtleties are:

  • adapting a single-element sequence hits edge cases with automatic attribute handling: Spirit Qi attribute propagation issue with single-member struct
  • Spirit needs hand-holding in this particular case to treat the repeat[...]>>value as synthesizing a single container /atomically/. The as<T> directive solves that here

boost::spirit::qi Rules with identical simple adapted struct attributes give compile error

This is again a problem with adapting single element sequences. Unfortunately I don't know a workaround for this case. In this situation you can remove the adaptation of B and specialize transform_attribute. There is a simple example here that does almost exactly what you want.

#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>

#include <string>

namespace qi = boost::spirit::qi;

struct A {
int index;
std::string name;
};
BOOST_FUSION_ADAPT_STRUCT(::A, (int, index)(std::string, name))


Related Topics



Leave a reply



Submit