Search code examples
c++boostboost-spiritboost-variantboost-proto

Compilation error by switching to higher Boost Version 1.6.1


I switched my Boost version from 1.6.1 to >=1.6.2 and my boost::spirit parser code fails to compile. Actually, I thinking the problem has something to do with a bug fix in Boost Variant from version 1.6.1 to version 1.6.2.

Release notes of version 1.6.2 say:

Variant constructors and assignment operators now do not participate in overload resolutions if variant can not hold the input type #5871, #11602

Here is a stripped version of my failing code:

Parser.h

#pragma once
#include <string>
#include <boost/variant.hpp>

struct AccTag {};

template <typename tag> struct unop;

typedef unop<AccTag> Acc;

typedef boost::variant<
    boost::recursive_wrapper<Acc>
> computationExpr;

typedef boost::variant<
    boost::recursive_wrapper<computationExpr>,
    int
> expr;

template <typename tag> struct unop
{
    unop() : oper1() {
    }
    explicit unop(const expr& o) : oper1(o) { }
    expr oper1;
};

expr parse(const std::string& expression, bool& ok);

Parser.cpp

#include "Parser.h"

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>

using namespace boost;

template <typename Iterator = std::string::iterator, typename Skipper = spirit::qi::space_type>
class ParserImpl : public spirit::qi::grammar<Iterator, expr(), Skipper>
{
public:

    ParserImpl() : ParserImpl::base_type(expr_)
    {
        using namespace boost::spirit::qi;  
        using namespace boost::phoenix;

        expr_          = props_.alias();

        props_ = (
            (lit("Acc") >> "(" >> int_ >> ")")[_val = construct<Acc>(_1) /* Most likely the source of the error */]
            );        

    }

    spirit::qi::rule<Iterator, expr(), Skipper> props_;
    spirit::qi::rule<Iterator, expr(), Skipper> expr_;
};


expr parse(const std::string& expression, bool& ok)
{
    expr result;
    std::string formula = expression;
    ParserImpl<> parser;
    auto b = formula.begin();
    auto e = formula.end();
    ok = spirit::qi::phrase_parse(b, e, parser, spirit::qi::space, result);
    if (b != e) {
        ok = false;
    }
    return result;

}

The code compiles without problems in Version 1.6.1, but fails in Version 1.6.2 with the error:

.../proto/transform/default.hpp(154): error C2679: Binary operator "=": ...

I guess in Version 1.6.1 there was an implicit conversion from computationExpr to expr, which is no longer allowed.

How can I fix this code? I think something in _val = construct<Acc>(_1) must be changed, but I'm lacking the skills to do it.


Solution

  • Indeed, the recursive_wrapper restricts the options for implicit construction more since 1.62:

    Wandbox on Boost 1.61

    boost::variant<int, boost::recursive_wrapper<std::string> > x;
    x = "worked before";
    std::cout << boost::get<std::string>(x) << "\n";
    

    Broken on Boost 1.62

    boost::variant<int, boost::recursive_wrapper<std::string> > x;
    x = "worked before";
    std::cout << boost::get<std::string>(x) << "\n";
    

    In this case, it's easy to fix: Fixed on Boost 1.62

    x = std::string("Hello world");
    

    Your Code

    In your code the nested use of recursive wrappers complicates things. The good news is, you don't need to have two layers. Just drop one:

    typedef boost::variant<
        int,
        computationExpr
    > expr;
    

    The instantiation is already sufficiently decoupled by the second recursive wrapper. Now, everything is fine again.

    Demo Time

    Note some style fixes/suggestions:

    Also, I reordered the elements in the expr variant because they were triggering infinite recursion on default construction.

    Live On Coliru

    #pragma once
    #include <string>
    #include <boost/variant.hpp>
    
    struct AccTag {};
    
    template <typename> struct unop;
    typedef unop<AccTag> Acc;
    
    typedef boost::variant<
        boost::recursive_wrapper<Acc>
    > computationExpr;
    
    typedef boost::variant<
        int,
        computationExpr
    > expr;
    
    template <typename> struct unop {
        unop() : oper1() { }
        explicit unop(const expr& o) : oper1(o) { }
        expr oper1;
    };
    
    expr parse(const std::string& expression, bool& ok);
    
    #include "Parser.h"
    
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/phoenix_operator.hpp>
    
    namespace qi = boost::spirit::qi;
    
    template <typename Iterator = std::string::const_iterator, typename Skipper = qi::space_type>
    class ParserImpl : public qi::grammar<Iterator, expr(), Skipper>
    {
    public:
    
        ParserImpl() : ParserImpl::base_type(expr_)
        {
            namespace phx = boost::phoenix;
            using namespace qi;
    
            expr_  = props_.alias();
    
            props_ =
                (lit("Acc") >> '(' >> int_ >> ')')[_val = phx::construct<Acc>(_1)]
                ;        
        }
    
      private:
        qi::rule<Iterator, expr(), Skipper> props_;
        qi::rule<Iterator, expr(), Skipper> expr_;
    };
    
    expr parse(const std::string& formula, bool& ok)
    {
        expr result;
        ParserImpl<> parser;
        auto b = formula.begin();
        auto e = formula.end();
        ok = qi::phrase_parse(b, e, parser >> qi::eoi, qi::space, result);
        return result;
    
    }
    
    static inline std::ostream& operator<<(std::ostream& os, Acc const& o) {
        return os << "Acc(" << o.oper1 << ")";
    }
    
    int main() {
        bool ok;
        auto e = parse("Acc (3)", ok);
    
        if (ok)
            std::cout << "Parsed: " << e << "\n";
        else
            std::cout << "Parse failed\n";
    }
    

    Prints

    Parsed: Acc(3)