visual-studioboostc++14boost-spirit-qi

Why this small change with my boost spirit rule requires to upgrade C++ compiler?


I'm using Visual Studio 2019 with std/c++14 setting. With the help from Sehe, this code compiled fine without any issue: Working Code

(Note that I switched to boost::optional and added ':' to the identifier charset)

Now I want to extend the grammar so that I can parse this syntax

Class Complex Complex_Name (
  Inherit Class1:Variable1
  Inherit Class1:Variable2
);

and output the following:

<class>
  <complex>
    <identifier>Complex_Name</identifier>
    <literal>" "</literal>
    <inherit>
      <identifier>Class1:Variable1</identifier>
    </inherit>
    <inherit>
      <identifier>Class1:Variable2</identifier>
    </inherit>
  </complex>
</class>

Below is what I have done

  1. Add New Inherit struct
struct Inherit {
  Id id;
};
  1. Add the new Inherit struct into Class variant
using Class = boost::variant<   
  Simple,                     
  Inherit,
  recursive_wrapper<Complex>, 
  recursive_wrapper<Container>
>;
  1. Now update the rule to pick this up
id_      = raw[alpha >> *(alnum | '_' | ':')];  // add ':' to identifier rule
...
type_      = simple_ | inherit_ | complex_ | container_;
...
inherit_ = lit("Inherit") >> id_
...
qi::rule<It, Ast::Inherit(), Skipper>     inherit_;
  1. Update XML rule to generate the content
void apply(Node parent, Inherit const& inh) const {
  auto inherit_ = named_child(parent, "inherit");
  apply(inherit_, inh.id);
}

FULL CODE

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

namespace qi = boost::spirit::qi;

namespace Ast {
    using boost::recursive_wrapper;

    template <typename> struct flavour_hack : std::char_traits<char> {};
    template <typename Tag>
        using String = std::basic_string<char, flavour_hack<Tag> >;

    using Id       = String<struct TagId>;
    using Literal  = String<struct TagLiteral>;
    using Datatype = String<struct TagDatatype>;
    struct Base {
        Id      id;
        Literal literal;
    };

    using Enum = std::vector<Id>;

    using Number = double;
    using Value  = boost::variant<Literal, Number, Id>;

    struct Simple : Base {
        boost::optional<Enum>     enumeration;
        boost::optional<Datatype> datatype;
        boost::optional<Value>    default_;
    };

    struct Complex;
    struct Container;
    struct Inherit {
        Id id;
    };
    using Class = boost::variant<   
        Simple,                     
        Inherit,
        recursive_wrapper<Complex>, 
        recursive_wrapper<Container>
    >;

    using Classes = std::vector<Class>;
    struct Container : Base { Class   element; };
    struct Complex   : Base { Classes members; };

    using Task = std::vector<Class>;
} // namespace Ast

BOOST_FUSION_ADAPT_STRUCT(Ast::Simple,    id, literal, enumeration, datatype, default_);
BOOST_FUSION_ADAPT_STRUCT(Ast::Complex,   id, literal, members)
BOOST_FUSION_ADAPT_STRUCT(Ast::Container, id, literal, element)

namespace Parser {
    template <typename It> struct Task : qi::grammar<It, Ast::Task()> {
        Task() : Task::base_type(start) {
            using namespace qi;

            start = skip(space)[task_];

            // lexemes:
            id_      = raw[alpha >> *(alnum | '_' | ':')];
            literal_ = '"' > *('\\' >> char_ | ~char_('"')) > '"';

            auto optlit = copy(literal_ | attr(std::string(" "))); // weird, but okay

            task_      = *class_ > eoi;
            type_      = simple_ | inherit_ | complex_ | container_;
            class_     = lit("Class") > type_ > ';';
            simple_    = lit("Simple") >> id_ >> optlit >> -enum_ >> -datatype_ >> -default_;
            inherit_   = lit("Inherit") >> id_;
            complex_   = lit("Complex") >> id_ >> optlit >> '(' >> *type_ >> ')';
            container_ = lit("Container") >> id_ >> optlit >> '(' >> type_ > ')';
            enum_      = lit("enumeration") >> '(' >> -(id_ % ',') > ')';
            datatype_  = lit("datatype") >> id_;
            value_     = literal_ | number_ | id_;
            number_    = double_;
            default_   = lit("Default") >> value_;

            BOOST_SPIRIT_DEBUG_NODES(
                (task_)(class_)(type_)(simple_)(complex_)(container_)(enum_)(datatype_)
                (default_)(id_)(literal_)(value_)(number_)
            )
        }

      private:
        qi::rule<It, Ast::Task()> start;

        using Skipper = qi::space_type;
        qi::rule<It, Ast::Task(), Skipper>      task_;
        qi::rule<It, Ast::Class(), Skipper>     class_, type_;
        qi::rule<It, Ast::Simple(), Skipper>    simple_;
        qi::rule<It, Ast::Complex(), Skipper>   complex_;
        qi::rule<It, Ast::Container(), Skipper> container_;
        qi::rule<It, Ast::Enum(), Skipper>      enum_;
        qi::rule<It, Ast::Datatype(), Skipper>  datatype_;
        qi::rule<It, Ast::Value(), Skipper>     default_;
        qi::rule<It, Ast::Inherit(), Skipper>     inherit_;

        // lexemes:
        qi::rule<It, Ast::Id()>      id_;
        qi::rule<It, Ast::Literal()> literal_;
        qi::rule<It, Ast::Value()>   value_;
        qi::rule<It, Ast::Number()>  number_;
    };
}

#include <pugixml.hpp>
namespace Generate {
    using namespace Ast;

    struct XML {
        using Node = pugi::xml_node;

        // callable for variant visiting:
        template <typename T> void operator()(Node parent, T const& node) const { apply(parent, node); }

      private:
        template <typename... Ts>
        void apply(Node parent, boost::variant<Ts...> const& v) const {
            using std::placeholders::_1;
            boost::apply_visitor(std::bind(*this, parent, _1), v);
        }

        void apply(Node parent, Ast::Number const& num) const {
            named_child(parent, "num").text().set(num);
        }

        void apply(Node parent, Ast::Id const& id) const {
            named_child(parent, "identifier").text().set(id.c_str());
        }

        void apply(Node parent, Ast::Literal const& literal) const {
            named_child(parent, "literal").text().set(literal.c_str());
        }

        void apply(Node parent, Ast::Datatype const& datatype) const {
            named_child(parent, "datatype").text().set(datatype.c_str());
        }

        template <typename T> void apply(Node parent, boost::optional<T> const& opt) const {
            if (opt)
                apply(parent, *opt);
        }

        void apply(Node parent, Simple const& s) const {
            auto simple = named_child(parent, "simple");
            apply(simple, s.id);
            apply(simple, s.literal);
            apply(simple, s.enumeration);
            apply(simple, s.datatype);
            if (s.default_.has_value()) {
                apply(named_child(simple, "default"), *s.default_);
            }
        }

        void apply(Node parent, Enum const& e) const {
            auto enum_ = named_child(parent, "enumeration");
            for (auto& v : e)
                named_child(enum_, "word").text().set(v.c_str());
        }

        void apply(Node parent, Inherit const& inh) const {
            auto inherit = named_child(parent, "inherit");
            apply(inherit, inh.id);
        }

        void apply(Node parent, Complex const& c) const {
            auto complex_ = named_child(parent, "complex");
            apply(complex_, c.id);
            apply(complex_, c.literal);
            for (auto& m : c.members)
                apply(complex_, m);
        }

        void apply(Node parent, Container const& c) const {
            auto cont = named_child(parent, "container");
            apply(cont, c.id);
            apply(cont, c.literal);
            apply(cont, c.element);
        }

        void apply(Node parent, Task const& t) const {
            auto task = named_child(parent, "task");
            for (auto& c : t)
                apply(task.append_child("class"), c);
        }

      private:
        Node named_child(Node parent, std::string const& name) const {
            auto child = parent.append_child();
            child.set_name(name.c_str());
            return child;
        }
    };
} // namespace Generate

int main() { 
    using It = std::string_view::const_iterator;
    static const Parser::Task<It> p;
    static const Generate::XML to_xml;

    for (std::string_view input : {
        R"(
            Class Simple caption;
            Class Simple test enumeration(opt1, opt2, opt3, opt4);
            Class Simple my_var datatype restriction;
            Class Simple var2 Default 0;
            Class Complex complexType (
                Inherit Class1:Variable2
                Inherit Class1:Variable2
                Inherit Class2:Variable1
            );
        )"
    }) {
        try {
            Ast::Task t;

            if (qi::parse(begin(input), end(input), p, t)) {
                pugi::xml_document doc;
                to_xml(doc.root(), t);
                doc.print(std::cout, "  ", pugi::format_default);
                std::cout << std::endl;
            } else {
                std::cout << " -> INVALID" << std::endl;
            }
        } catch (qi::expectation_failure<It> const& ef) {
            auto f    = begin(input);
            auto p    = ef.first - input.begin();
            auto bol  = input.find_last_of("\r\n", p) + 1;
            auto line = std::count(f, f + bol, '\n') + 1;
            auto eol  = input.find_first_of("\r\n", p);

            std::cerr << " -> EXPECTED " << ef.what_ << " in line:" << line << "\n"
                << input.substr(bol, eol - bol) << "\n"
                << std::setw(p - bol) << ""
                << "^--- here" << std::endl;
        }
    }
}

When I compile this new update with current std/c++14 settings, it throw this error

Error   C2440   'static_cast': cannot convert from 'const T_' to 'Attribute'

Even though I'm not suppose to, I still tried to compiled this with std/c++17 options and get the same error. But if I compile this with std/c++20 option using Visual Studio 2022. Then it compiled and work as expected.

Same thing happen with Compiler Explorer. This will throw the same error when I'm using anything below x86-64 clang 16.0.0 option.

DEMO NEW CODE that fail using old compiler

If I switch to use x86-64 clang 16.0.0 option, it will compile and work just fine.

DEMO NEW CODE that work with latest compiler

What I added is fairly simple but not sure why it would not supported. Can somebody tell me what's wrong and why I need to use the latest compiler for this small change.

Thanks Dylan


Solution

  • it would not supported

    That's framing. It's supported. You just don't write enough code.

    C++20 adds () constructability of aggregates. Since you don't have it, either add the constructor to Inherit:

    Inherit(Id id = {}) : id(std::move(id)) {}
    

    OR adapt it like the other AST nodes:

    BOOST_FUSION_ADAPT_STRUCT(Ast::Inherit, id)
    

    Both work. Other notes:

    Live On Compiler Explorer

    // #define BOOST_SPIRIT_DEBUG 1
    #include <boost/fusion/adapted.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <iomanip>
    
    namespace qi = boost::spirit::qi;
    
    namespace Ast {
        using boost::recursive_wrapper;
    
        template <typename> struct flavour_hack : std::char_traits<char> {};
        template <typename Tag>
            using String = std::basic_string<char, flavour_hack<Tag> >;
    
        using Id       = String<struct TagId>;
        using Literal  = String<struct TagLiteral>;
        using Datatype = String<struct TagDatatype>;
        struct Base {
            Id      id;
            Literal literal;
        };
    
        using Ids  = std::vector<Id>;
        using Enum = Ids;
    
        using Number = double;
        using Value  = boost::variant<Literal, Number, Id>;
    
        struct Simple : Base {
            boost::optional<Enum>     enumeration;
            boost::optional<Datatype> datatype;
            boost::optional<Value>    default_;
        };
    
        struct Complex;
        struct Container;
    ;
        using Class = boost::variant<   
            Simple,                     
            recursive_wrapper<Complex>, 
            recursive_wrapper<Container>
        >;
    
        using Classes = std::vector<Class>;
        struct Container : Base { Class element; };
        struct Complex   : Base { Ids bases; Classes members; };
    
        using Task = std::vector<Class>;
    } // namespace Ast
    
    BOOST_FUSION_ADAPT_STRUCT(Ast::Simple,    id, literal, enumeration, datatype, default_);
    BOOST_FUSION_ADAPT_STRUCT(Ast::Complex,   id, literal, bases, members)
    BOOST_FUSION_ADAPT_STRUCT(Ast::Container, id, literal, element)
    
    namespace Parser {
        template <typename It> struct Task : qi::grammar<It, Ast::Task()> {
            Task() : Task::base_type(start) {
                using namespace qi;
    
                start = skip(space)[task_];
    
                // lexemes:
                id_      = raw[alpha >> *(alnum | '_' | ':')];
                literal_ = '"' > *('\\' >> char_ | ~char_('"')) > '"';
    
                auto optlit = copy(literal_ | attr(std::string(" "))); // weird, but okay
    
                task_      = *class_ > eoi;
                type_      = simple_ | complex_ | container_;
                class_     = lit("Class") > type_ > ';';
                simple_    = lit("Simple") >> id_ >> optlit >> -enum_ >> -datatype_ >> -default_;
                inherit_   = lit("Inherit") >> id_;
                complex_   = lit("Complex") >> id_ >> optlit >> '(' >> *inherit_ >> *type_ >> ')';
                container_ = lit("Container") >> id_ >> optlit >> '(' >> type_ > ')';
                enum_      = lit("enumeration") >> '(' >> -(id_ % ',') > ')';
                datatype_  = lit("datatype") >> id_;
                value_     = literal_ | number_ | id_;
                number_    = double_;
                default_   = lit("Default") >> value_;
    
                BOOST_SPIRIT_DEBUG_NODES(
                    (task_)(class_)(type_)(simple_)(complex_)(container_)(enum_)(datatype_)
                    (default_)(id_)(literal_)(value_)(number_)(inherit_)
                )
            }
    
          private:
            qi::rule<It, Ast::Task()> start;
    
            using Skipper = qi::space_type;
            qi::rule<It, Ast::Task(), Skipper>      task_;
            qi::rule<It, Ast::Class(), Skipper>     class_, type_;
            qi::rule<It, Ast::Simple(), Skipper>    simple_;
            qi::rule<It, Ast::Complex(), Skipper>   complex_;
            qi::rule<It, Ast::Container(), Skipper> container_;
            qi::rule<It, Ast::Enum(), Skipper>      enum_;
            qi::rule<It, Ast::Datatype(), Skipper>  datatype_;
            qi::rule<It, Ast::Value(), Skipper>     default_;
            qi::rule<It, Ast::Id(), Skipper>        inherit_;
    
            // lexemes:
            qi::rule<It, Ast::Id()>      id_;
            qi::rule<It, Ast::Literal()> literal_;
            qi::rule<It, Ast::Value()>   value_;
            qi::rule<It, Ast::Number()>  number_;
        };
    }
    
    #include <pugixml.hpp>
    namespace Generate {
        using namespace Ast;
    
        struct XML {
            using Node = pugi::xml_node;
    
            // callable for variant visiting:
            template <typename T> void operator()(Node parent, T const& node) const { apply(parent, node); }
    
          private:
            template <typename... Ts>
            void apply(Node parent, boost::variant<Ts...> const& v) const {
                using std::placeholders::_1;
                boost::apply_visitor(std::bind(*this, parent, _1), v);
            }
    
            void apply(Node parent, Ast::Number const& num) const {
                named_child(parent, "num").text().set(num);
            }
    
            void apply(Node parent, Ast::Id const& id) const {
                named_child(parent, "identifier").text().set(id.c_str());
            }
    
            void apply(Node parent, Ast::Literal const& literal) const {
                named_child(parent, "literal").text().set(literal.c_str());
            }
    
            void apply(Node parent, Ast::Datatype const& datatype) const {
                named_child(parent, "datatype").text().set(datatype.c_str());
            }
    
            template <typename T> void apply(Node parent, boost::optional<T> const& opt) const {
                if (opt)
                    apply(parent, *opt);
            }
    
            void apply(Node parent, Simple const& s) const {
                auto simple = named_child(parent, "simple");
                apply(simple, s.id);
                apply(simple, s.literal);
                apply(simple, s.enumeration);
                apply(simple, s.datatype);
                if (s.default_.has_value()) {
                    apply(named_child(simple, "default"), *s.default_);
                }
            }
    
            void apply(Node parent, Enum const& e) const {
                auto enum_ = named_child(parent, "enumeration");
                for (auto& v : e)
                    named_child(enum_, "word").text().set(v.c_str());
            }
    
            void apply(Node parent, Complex const& c) const {
                auto complex_ = named_child(parent, "complex");
                apply(complex_, c.id);
                for (auto& base : c.bases)
                    apply(named_child(complex_, "inherit"), base);
                apply(complex_, c.literal);
                for (auto& m : c.members)
                    apply(complex_, m);
            }
    
            void apply(Node parent, Container const& c) const {
                auto cont = named_child(parent, "container");
                apply(cont, c.id);
                apply(cont, c.literal);
                apply(cont, c.element);
            }
    
            void apply(Node parent, Task const& t) const {
                auto task = named_child(parent, "task");
                for (auto& c : t)
                    apply(task.append_child("class"), c);
            }
    
          private:
            Node named_child(Node parent, std::string const& name) const {
                auto child = parent.append_child();
                child.set_name(name.c_str());
                return child;
            }
        };
    } // namespace Generate
    
    int main() { 
        using It = std::string::const_iterator;
        static const Parser::Task<It> p;
        static const Generate::XML to_xml;
    
        for (std::string const input : {
            R"(
                Class Simple caption;
                Class Simple test enumeration(opt1, opt2, opt3, opt4);
                Class Simple my_var datatype restriction;
                Class Simple var2 Default 0;
                Class Complex complexType (
                    Inherit Class1:Variable2
                    Inherit Class1:Variable2
                    Inherit Class2:Variable1
                );
            )"
        }) {
            try {
                Ast::Task t;
    
                if (qi::parse(begin(input), end(input), p, t)) {
                    pugi::xml_document doc;
                    to_xml(doc.root(), t);
                    doc.print(std::cout, "  ", pugi::format_default);
                    std::cout << std::endl;
                } else {
                    std::cout << " -> INVALID" << std::endl;
                }
            } catch (qi::expectation_failure<It> const& ef) {
                auto f    = begin(input);
                auto p    = ef.first - input.begin();
                auto bol  = input.find_last_of("\r\n", p) + 1;
                auto line = std::count(f, f + bol, '\n') + 1;
                auto eol  = input.find_first_of("\r\n", p);
    
                std::cerr << " -> EXPECTED " << ef.what_ << " in line:" << line << "\n"
                    << input.substr(bol, eol - bol) << "\n"
                    << std::setw(p - bol) << ""
                    << "^--- here" << std::endl;
            }
        }
    }
    

    Prints

    <task>
      <class>
        <simple>
          <identifier>caption</identifier>
          <literal> </literal>
        </simple>
      </class>
      <class>
        <simple>
          <identifier>test</identifier>
          <literal> </literal>
          <enumeration>
            <word>opt1</word>
            <word>opt2</word>
            <word>opt3</word>
            <word>opt4</word>
          </enumeration>
        </simple>
      </class>
      <class>
        <simple>
          <identifier>my_var</identifier>
          <literal> </literal>
          <datatype>restriction</datatype>
        </simple>
      </class>
      <class>
        <simple>
          <identifier>var2</identifier>
          <literal> </literal>
          <default>
            <num>0</num>
          </default>
        </simple>
      </class>
      <class>
        <complex>
          <identifier>complexType</identifier>
          <inherit>
            <identifier>Class1:Variable2</identifier>
          </inherit>
          <inherit>
            <identifier>Class1:Variable2</identifier>
          </inherit>
          <inherit>
            <identifier>Class2:Variable1</identifier>
          </inherit>
          <literal> </literal>
        </complex>
      </class>
    </task>
    

    Note that