Search code examples
c++boostgrammarboost-spirit

Error when adapting a class with BOOST_FUSION_ADAPT_ADT


I've the following class:

#ifndef WFRACTAL_FRACTAL_METADATA_H_
#define WFRACTAL_FRACTAL_METADATA_H_

#include <string>

namespace WFractal {
namespace Fractal {

class Metadata {

public:

    void setAuthorName(const std::string &name);
    void setAuthorEMail(const std::string &email);
    void setBriefDescription(const std::string &brief);
    void setCompleteDescription(const std::string &description);
    std::string getAuthorName() const;
    std::string getAuthorEMail() const;
    std::string getBriefDescription() const;
    std::string getCompleteDescription() const;

public:

    bool operator==(const Metadata &other);

private:

    std::string m_name;
    std::string m_email;
    std::string m_brief;
    std::string m_description;
};

} // namespace Fractal
} // namespace WFractal

#endif // !WFRACTAL_FRACTAL_METADATA_H_

I want to parse it using boost::spirit from following file content:

metadata {
  author = "Me";
  email = "myemail";
  brief = "brief description";
  description = "complete description";
}

I've read from this page that I can use BOOST_FUSION_ADAPT_STRUCT in order to parse it. This is my grammar template:

#ifndef WFRACTAL_FRACTAL_PARSER_METADATAGRAMMAR_H_
#define WFRACTAL_FRACTAL_PARSER_METADATAGRAMMAR_H_

#include <boost/fusion/adapted/adt/adapt_adt.hpp>
#include <boost/fusion/include/adapt_adt.hpp>
#include <boost/spirit/include/qi_no_case.hpp>
#include <string>
#include "Fractal/Metadata.h"

BOOST_FUSION_ADAPT_ADT(
    WFractal::Fractal::Metadata,
    (obj.getAuthorName(), obj.setAuthorName(val))
    (obj.getAuthorEMail(), obj.setBriefDescription(val))
    (obj.getBriefDescription(), obj.setCompleteDescription(val))
    (obj.getCompleteDescription(), obj.setAuthorEMail(val))
)

namespace WFractal {
namespace Fractal {
namespace Parser {

template <typename Iterator>
struct MetadataParser : boost::spirit::qi::grammar<Iterator, Metadata(), boost::spirit::ascii::space_type> {
    MetadataParser() : MetadataParser::base_type(start) {
        using boost::spirit::qi::int_;
        using boost::spirit::qi::lit;
        using boost::spirit::qi::double_;
        using boost::spirit::qi::lexeme;
        using boost::spirit::ascii::char_;
        using boost::spirit::ascii::no_case;

        quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'];

        start %=
        no_case[lit("metadata")]
        >> '{'
        >>  no_case[lit("author")] >> '=' >> quoted_string >> ';'
        >>  no_case[lit("email")] >> '=' >> quoted_string >> ';'
        >>  no_case[lit("brief")] >> '=' >> quoted_string >> ';'
        >>  no_case[lit("description")] >> '=' >> quoted_string >> ';'
        >>  '}'
        ;
    }

    boost::spirit::qi::rule<Iterator, std::string(), boost::spirit::ascii::space_type> quoted_string;
    boost::spirit::qi::rule<Iterator, Metadata(), boost::spirit::ascii::space_type> start;
};

} // namespace Parser
} // namespace Fractal
} // namespace WFractal

#endif // !WFRACTAL_FRACTAL_PARSER_METADATAGRAMMAR_H_

When I create an instance of this grammar (always following the page example), I obtain compiler error:

typedef string::const_iterator StringIterator;
typedef Parser::MetadataParser<StringIterator> MetadataParser;
MetadataParser parser;

I obtain a lot of errors (typical of boost...) and I've noticed inside them many copy of this error:

src/Fractal/FileFactory.cpp:27:17:   required from here
/usr/include/boost/spirit/home/qi/detail/assign_to.hpp:152:18: error: no matching function for call to ‘boost::fusion::extension::adt_attribute_proxy<WFractal::Fractal::Metadata, 0, false>::adt_attribute_proxy(const std::__cxx11::basic_string<char>&)’
             attr = static_cast<Attribute>(val);

What I'm doing wrong? How can I fix it?

EDIT

As suggested by m.s. I've added the #include <boost/spirit/include/support_adapt_adt_attributes.hpp> header but this is not working.

#ifndef WFRACTAL_FRACTAL_PARSER_METADATAPARSER_H_
#define WFRACTAL_FRACTAL_PARSER_METADATAPARSER_H_

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/fusion/adapted/adt/adapt_adt.hpp>
#include <boost/fusion/include/adapt_adt.hpp>
#include <boost/spirit/include/support_adapt_adt_attributes.hpp>
#include <string>
#include "Fractal/Metadata.h"

BOOST_FUSION_ADAPT_ADT(
    WFractal::Fractal::Metadata,
    (obj.getAuthorName(), obj.setAuthorName(val))
    (obj.getAuthorEMail(), obj.setAuthorEMail(val))
    (obj.getBriefDescription(), obj.setBriefDescription(val))
    (obj.getCompleteDescription(), obj.setCompleteDescription(val))
)

namespace WFractal {
namespace Fractal {
namespace Parser {

template <typename Iterator>
struct MetadataParser : boost::spirit::qi::grammar<Iterator, Metadata(), boost::spirit::ascii::space_type> {
    MetadataParser() : MetadataParser::base_type(start) {
        using boost::spirit::qi::lit;
        using boost::spirit::qi::lexeme;
        using boost::spirit::ascii::char_;
        using boost::spirit::ascii::no_case;

        quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'];

        start %=
        no_case[lit("metadata")]
        >> '{'
        >> ((no_case[lit("author")] >> '=' >> quoted_string >> ';')
        ^  (no_case[lit("email")] >> '=' >> quoted_string >> ';')
        ^  (no_case[lit("brief")] >> '=' >> quoted_string >> ';')
        ^  (no_case[lit("description")] >> '=' >> quoted_string >> ';'))
        >>  '}'
        ;
    }

    boost::spirit::qi::rule<Iterator, std::string(), boost::spirit::ascii::space_type> quoted_string;
    boost::spirit::qi::rule<Iterator, Metadata(), boost::spirit::ascii::space_type> start;
};

} // namespace Parser
} // namespace Fractal
} // namespace WFractal

#endif // !WFRACTAL_FRACTAL_PARSER_METADATAPARSER_H_

Solution

  • Hmm.

    After very long struggle with Modular Boost and git... I've found that this is a regression.

    The regression has been partially fixed by this pull request:

    Sadly, it's still broken in the presence of the permutation parser. I've tested with BOOST_FUSION_ADAPT_STRUCT to verify that it is not broken in some other way.

    I'll add the reduced sample to the PR for comments. Meanwhile, here's the reproducer made selfcontained:

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/adapted.hpp>
    #include <iostream>
    
    class Foo {
      public:
        char const& getA() const { return a; } 
        char const& getB() const { return b; } 
    
        void setA(char value)    { a = value; } 
        void setB(char value)    { b = value; } 
      private:
        char a, b;
    };
    
    BOOST_FUSION_ADAPT_ADT(
            Foo,
            (char const&, char const&, obj.getA(), obj.setA(val))
            (char const&, char const&, obj.getB(), obj.setB(val))
        )
    
    
    int main() {
        namespace qi = boost::spirit::qi;
    
        boost::spirit::istream_iterator f(std::cin), l; // input e.g. "a=a;b=b;"
        Foo foo;
    
        bool r = qi::parse(f, l,
    #if 0 // FAILS TO COMPILE
                          ("a=" >> qi::char_ >> ';')
                        ^ ("b=" >> qi::char_ >> ';')
    #else // COMPILES WITH FIX FROM PR #153
                          ("a=" >> qi::char_ >> ';')
                       >> ("b=" >> qi::char_ >> ';')
    #endif 
                    , foo);
    
        if (r)
            std::cout << "Parsed: " << boost::fusion::as_vector(foo) << "\n";
        else
            std::cerr << "Parse failed\n";
    }
    

    If you, however, change the #if 1 to #if 0, you get the output:

    Parsed: (a b)
    

    Note of course that's assuming you use a branch that has the PR 153 applied.