Search code examples
c++templatesboostboost-mplboost-variant

Derive from `boost::static-visitor` to remove code duplication


In one of my projects I'm using boost-variant excessively. At some point I exceeded the maximum number of template parameters (20) for boost-variant. Hence, I derived the following solution by linking several boost-variant Types together like a linked-list.

#include <boost/variant.hpp>
#include <iostream>

template<int T> struct A {
    const int value = T;
};

typedef boost::variant<
    A<20>,A<21>,A<22>,A<23>,A<24>,A<25>,A<26>,A<27>,A<28>,A<29>,A<30>,A<31>,A<32>,A<33>,A<34>,A<35>,A<36>,A<37>,A<38>,A<39>
> NextVar;

typedef boost::variant<
    A<1>,A<2>,A<3>,A<4>,A<5>,A<6>,A<7>,A<8>,A<9>,A<10>,A<11>,A<12>,A<13>,A<14>,A<15>,A<16>,A<17>,A<18>,A<19>,NextVar
> TVar;

struct PrintVisitor : public boost::static_visitor<std::string> {
    result_type operator()(const NextVar& n) {
        return n.apply_visitor(*this);
    }

    template<int T>
    result_type operator()(const A<T>& a)  {
        return std::to_string(a.value);
    }
};

struct IntVisitor : public boost::static_visitor<int> {
    result_type operator()(const NextVar& n) {
        return n.apply_visitor(*this);
    }

    template<int T>
    result_type operator()(const A<T>& a) {
        return a.value;
    }
};

template<int I>
struct AddVisitor : public boost::static_visitor<int> {
    result_type operator()(const NextVar& n) {
        return n.apply_visitor(*this);
    }

    template<int T>
    result_type operator()(const A<T>& a) {
        return a.value+I;
    }
};

int main(int argc, char **args) {
    TVar x = A<35>(); 
    PrintVisitor v1;
    std::cout << x.apply_visitor(v1) << std::endl;
    IntVisitor v2;
    std::cout << x.apply_visitor(v2) << std::endl;
    AddVisitor<10> v3;
    std::cout << x.apply_visitor(v3) << std::endl;
}

I was really surprised how well this workaround solved my problem. Still there was a grain of salt. For every visitor I had to include the line:

result_type operator()(const NextVar& n) {
    return n.apply_visitor(*this);
}

Which seems to be a kind of unnecessary code duplication. Getting even worse, if I'm in need of 60 or even more types in boost-variant. My try was to define a common base class for all of my visitors:

template<typename T>
struct BaseVisitor : public boost::static_visitor<T> {
    result_type operator()(const NextVar& n) {
        return n.apply_visitor(*this);
    }
};

I thought, that deriving from BaseVisitor like shown below would solve the problem:

struct PrintVisitor : public BaseVisitor<std::string> {
    template<int T>
    result_type operator()(const A<T>& a)  {
        return std::to_string(a.value);
    }
};

But instead the compiler complains:

template-argument for "const A<T> &" could not be derived from "T19" 

What might be the closest workaround for this kind of problem?


Solution

  • First of all, you could simply increase this limit of 20 fixed by BOOST_MPL_LIMIT_LIST_SIZE.

    About your code: even if it compiles, BaseVisitor::operator() will make an infinite recursion besause of *this considered as a BaseVisitor at that moment.
    To avoid that, you can use CRTP to have the good derived() instead:

    template<class Derived, typename T>
    struct BaseVisitor : public boost::static_visitor<T> {
        using typename boost::static_visitor<T>::result_type;
    
        Derived & derived() { return static_cast<Derived &>(*this); } 
    
        result_type operator()(const NextVar& n) {
            return n.apply_visitor( derived() );
        }
    };
    

    Then bringing this same operator() into the scope (otherwise hidden by the new one) of your derived classes (as well as result_type needed for template classes).

    DEMO

    Without aliases

    As said in the comments, the aliases have to be written in each derived class. To get rid of it, we can gather the two operator()s at the same level in the base class, and name the derived function differently (visit here):

    template<class Derived, typename T>
    struct BaseVisitor : public boost::static_visitor<T> {
        using typename boost::static_visitor<T>::result_type;
    
        Derived & derived() { return static_cast<Derived &>(*this); } 
    
        result_type operator()(const NextVar& n) {
            return n.apply_visitor( derived() );
        }
        template<int I>
        result_type operator()(const A<I>& a)  {
            return derived().visit(a);
        }
    };
    

    Leaving us with:

    struct PrintVisitor : public BaseVisitor<PrintVisitor, std::string> {
        template<int I>
        std::string visit(const A<I>& a)  {
            return std::to_string(a.value);
        }
    };
    

    DEMO