c++boostboost-variantapply-visitor

avoiding redundant code in boost::variant visitors


I am facing the following problem: I have some visitors on for boost::variant, which all doing the same for a specific type, here foo, so the method

void operator()(const foo& ast)
{
    //allways the same
}

is allways the same in every single visitor. Since I dont want to write this redundant method in all the visitors, I tried to avoid this adding a common base class, which implements this method, to all the visitors. Problem the method calls the visitor itself recursivly, like this:

void operator(const foo& ast)
{
    for(auto&& item : ast.members)
    {
        boost::apply_visitor(*this, item);
    }
}

and since all the other methods, which are matching for the members arent implemented in the base class, I get an compiler error, on this. Now my question is, how can I get rid of my redundant code?

Here is an example on how the problem could look:

struct variant_one;
struct variant_two;
struct nil{};
typedef boost::variant<
    boost::spirit::x3::forward_ast<variant_one>, 
    boost::spirit::x3::forward_ast<variant_two>,
    nil
> example_variant;

struct variant_one {};
struct variant_two 
{
    std::vector<example_variant> members;
};


struct visitor_one : boost::static_visitor<void>
{
    void operator()(const variant_one& ast)
    {
        std::cout << "visitor_one detected var_one" << std::endl;
    }

    //this is the redundant method
    void operator()(const variant_two& ast)
    {
        std::cout << "visitor detected var_two, output members:" <<std::endl;
        for(auto&& member : ast.members)
        {
            boost::apply_visitor(*this, member);
        }
    }
}

struct visitor_two : boost::static_visitor<void>
{

    void operator()(const variant_one& ast)
    {
        std::cout << "visitor_one detected var_two" << std::endl;
    }

    //this is the redundant method
    void operator()(const variant_two& ast)
    {
        std::cout << "visitor detected var_two, output members:" <<std::endl;
        for(auto&& member : ast.members)
        {
            boost::apply_visitor(*this, member);
        }
    }
}

Solution

  • Something like this?

    template<typename Derived>
    struct VisitorBase {
        void operator()(const foo& ast) {
            for(auto&& item : ast.members) {
                boost::apply_visitor(*static_cast<Derived*>(this), item);
            }
        }
    };
    
    struct VisitorA : VisitorBase<VisitorA> {
        void operator()(const ItemType& item) {
             // do stuff
        }
    };
    

    or if the types used in visitors are the same/known beforehand and virtual functions are fine:

    struct VisitorBase {
        void operator()(const foo& ast) {
            for(auto&& item : ast.members) {
                boost::apply_visitor(*this, item);
            }
        }
        virtual void operator()(const ItemTypeA&) = 0;
        virtual void opetator()(const ItemTypeB&) = 0;
    };
    
    struct VisitorA : VisitorBase {
        void operator()(const ItemTypeA& item) {
             // do stuff
        }
        void operator()(const ItemTypeB& item) {
             // do stuff
        }
    };
    

    In the first example you might want to make sure that you don't accidentally instantiate the template with a non-derived type, for example with this:

    static_assert(std::is_base_of<VisitorBase,Derived>::value, "Derived should be derived from VisitorBase");
    

    That will still leave open the possibility of instantiating a VisitorBase-derived type with a different VisitorBase-derived type in the template parameter, leading to undefined behavior. So be careful.