Search code examples
c++boostboost-spiritboost-spirit-qi

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


I want to reuse a rule, and just prepend it with a keyword. So my rules look like this:

#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))

struct B {
  A data;
};
BOOST_FUSION_ADAPT_STRUCT(::B, (A, data))

int main() {
  typedef std::string::const_iterator iterator_type;

  qi::rule<iterator_type, A()> a_rule = qi::int_ > qi::lit(",") > *(qi::char_);
  qi::rule<iterator_type, B()> b_rule = qi::lit("(") > a_rule > qi::lit(")");
  qi::rule<iterator_type, B()> bad_rule = qi::lit("keyword") > b_rule;

  return 0;
}

This doesn't compile, since the compiler wants to create A from B (referring to the definition of the bad_rule):

C:/tc/gcc_x64_4.8.1_win32_seh_rev1/mingw64/my/src/boost_1_54_0/boost/spirit/home/qi/detail/assign_to.hpp:152:18: error: no matching function for call to 'A::A(const B&)'
             attr = static_cast<Attribute>(val);
                  ^

However, changing the struct B to be not so simple, makes it work:

#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))

struct B {
  A data;
  int dummy;
};
BOOST_FUSION_ADAPT_STRUCT(::B, (A, data)(int, dummy))

int main() {
  typedef std::string::const_iterator iterator_type;

  qi::rule<iterator_type, A()> a_rule = qi::int_ > qi::lit(",") > *(qi::char_);
  qi::rule<iterator_type, B()> b_rule = qi::lit("(") > a_rule > qi::lit(")") > qi::attr(0);
  qi::rule<iterator_type, B()> bad_rule = qi::lit("keyword") > b_rule;

  return 0;
}

Any ideas, how to get along without this workaround and what is going on here?


Solution

  • 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))
    
    struct B {
      A data;
    };
    
    namespace boost{ namespace spirit{ namespace traits
    {
        template <>
        struct transform_attribute<B,A,qi::domain>
        {
            typedef A& type;
    
            static type pre(B& val){ return val.data;}
            static void post(B&, A const&){}  
            static void fail(B&){} 
        };
    }}}
    
    int main() {
      typedef std::string::const_iterator iterator_type;
    
      qi::rule<iterator_type, A()> a_rule = qi::int_ >> qi::lit(",") >> *(qi::char_-qi::lit(")"));
      qi::rule<iterator_type, B()> b_rule = qi::lit("(") >> a_rule >> qi::lit(")");
      qi::rule<iterator_type, B()> bad_rule = qi::lit("keyword") >> b_rule;
    
      std::string test="keyword(1,one)";
      iterator_type iter=test.begin(), end=test.end();
    
      B b;
    
      bool result = qi::parse(iter,end,bad_rule,b);
      if(result && iter==end)
      {
        std::cout << "Success:" << std::endl;
        std::cout << "b.data.index=" << b.data.index << ", b.data.name=" << b.data.name << std::endl;
      }
      else
      {
        std::cout << "Failed. Unparsed: " << std::string(iter,end) << std::endl;
      }
    
      return 0;
    }