Search code examples
c++boostboost-variantboost-spirit-karma

casting attribute to boost::variant


While learning how to use boost spirit, phoenix and fusion libraries, I came accross this minimal example that does not compile on msvc (2015, version 14) and boost 1.61.0

#include <boost/spirit/include/karma.hpp>
#include <boost/variant/variant.hpp>

namespace ka = boost::spirit::karma;

struct U /* a kind of union (legacy code)*/
    {
        bool kind;
        double foo; /* if kind = true */
        size_t bar; /* if kind = false */
    };

typedef boost::variant<double, size_t> UVariant;

namespace boost { namespace spirit { namespace traits {
    template<>
    struct transform_attribute<U,UVariant,ka::domain>
    {
        typedef UVariant type;
        static type pre(U & u) { 
            switch (u.kind)
            { 
            case true: 
                return type(u.foo); 
            case false: 
                return type(u.bar);
            }
        }
    };
}}}

typedef std::back_insert_iterator<std::string> iterator;

class grm: public ka::grammar<iterator, U()>
{
public:
    grm():grm::base_type(start)
    {
        start = ka::attr_cast<U,UVariant >(foo | bar);
        foo = ka::double_;
        bar = ka::uint_;
        */
    }
private:
    ka::rule<iterator,U()> start;
    ka::rule<iterator,double()> foo;
    ka::rule<iterator,size_t()> bar;
};

int main(int argc, char * argv[])
{
    grm g;
    U u;
    u.kind = true;
    u.foo = 1.0;

    std::string generated;
    std::back_insert_iterator<std::string> sink(generated);
    ka::generate(sink,g,u);


    return 0;
}

Then I get the following error message:

error C2665: 'boost::detail::variant::make_initializer_node::apply::initializer_node::initialize' : none of the 5 overloads could convert all arguments types

A similar issue has been reported here although I could not understand how the answer address the issue and whether this is really the same issue as it seems all types are correctly provided (no need for type conversions).


Solution

  • The problem seems to be that Spirit isn't picking your custom transform_attribute customization point. It's using the default one, and that tries to construct a boost::variant<double,size_t> from a const U(!!) and that obviously fails.

    Karma always works with const values internally so you need to change your specialization of transform_attribute to:

    namespace boost { namespace spirit { namespace traits {
        template<>
        struct transform_attribute<const U,UVariant,ka::domain>
                                   ^^^^^^^
        {
            typedef UVariant type;
            static type pre(const U & u) {
                            ^^^^^^^ 
                //same as before
            }
        };
    }}}
    

    and then it will be picked up by Karma and everything will work.

    Full sample (On rextester):

    #include <boost/spirit/include/karma.hpp>
    #include <boost/variant/variant.hpp>
    
    namespace ka = boost::spirit::karma;
    
    struct U /* a kind of union (legacy code)*/
        {
            bool kind;
            double foo; /* if kind = true */
            size_t bar; /* if kind = false */
        };
    
    typedef boost::variant<double, size_t> UVariant;
    
    namespace boost { namespace spirit { namespace traits {
        template<>
        struct transform_attribute<const U,UVariant,ka::domain>
        {
            typedef UVariant type;
            static type pre(const U & u) { 
                if(u.kind)
                { 
                    return type(u.foo); 
                }
                else
                {
                    return type(u.bar);
                }
            }
        };
    }}}
    
    typedef std::back_insert_iterator<std::string> iterator;
    
    class grm: public ka::grammar<iterator, U()>
    {
    public:
        grm():grm::base_type(start)
        {
            start = ka::attr_cast< UVariant >(foo | bar);
            foo = ka::double_;
            bar = ka::uint_;
        }
    private:
        ka::rule<iterator,U()> start;
        ka::rule<iterator,double()> foo;
        ka::rule<iterator,size_t()> bar;
    };
    
    int main(int argc, char * argv[])
    {
        grm g;
        U u;
        u.kind = false;
        u.foo = 1.0;
        u.bar = 34;
    
        std::string generated;
        std::back_insert_iterator<std::string> sink(generated);
        ka::generate(sink,g,u);
    
        std::cout << generated << std::endl;
    
    
        return 0;
    }