C++ template to cover const and non-const method

paperjam picture paperjam · Oct 17, 2011 · Viewed 13.7k times · Source

I have a problem with duplication of identical code for const and non-const versions. I can illustrate the problem with some code. Here are two sample visitors, one which modifies the visited objects and one which does not.

struct VisitorRead 
{
    template <class T>
    void operator()(T &t) { std::cin >> t; }
};

struct VisitorWrite 
{
    template <class T> 
    void operator()(const T &t) { std::cout << t << "\n"; }
};

Now here is an aggregate object - this has just two data members but my actual code is much more complex:

struct Aggregate
{
    int i;
    double d;

    template <class Visitor>
    void operator()(Visitor &v)
    {
        v(i);
        v(d);
    }
    template <class Visitor>
    void operator()(Visitor &v) const
    {
        v(i);
        v(d);
    }
};

And a function to demonstrate the above:

static void test()
{
    Aggregate a;
    a(VisitorRead());
    const Aggregate b(a);
    b(VisitorWrite());
}

Now, the problem here is the duplication of Aggregate::operator() for const and non-const versions.

Is it somehow possible to avoid duplication of this code?

I have one solution which is this:

template <class Visitor, class Struct>
void visit(Visitor &v, Struct &s) 
{
    v(s.i);
    v(s.i);
}

static void test2()
{
    Aggregate a;
    visit(VisitorRead(), a);
    const Aggregate b(a);
    visit(VisitorWrite(), b);
}

This means neither Aggregate::operator() is needed and there is no duplication. But I am not comfortable with the fact that visit() is generic with no mention of type Aggregate.

Is there a better way?

Answer

David Rodr&#237;guez - dribeas picture David Rodríguez - dribeas · Oct 17, 2011

I tend to like simple solutions, so I would go for the free-function approach, possibly adding SFINAE to disable the function for types other than Aggregate:

template <typename Visitor, typename T>
typename std::enable_if< std::is_same<Aggregate,
                                   typename std::remove_const<T>::type 
                                  >::value
                       >::type
visit( Visitor & v, T & s ) {  // T can only be Aggregate or Aggregate const
    v(s.i);
    v(s.d);   
}

Where enable_if, is_same and remove_const are actually simple to implement if you don't have a C++0x enabled compiler (or you can borrow them from boost type_traits)

EDIT: While writing the SFINAE approach I realized that there are quite a few problems in providing the plain templated (no SFINAE) solution in the OP, which include the fact that if you need to provide more than one visitable types, the different templates would collide (i.e. they would be as good a match as the others). By providing SFINAE you are actually providing the visit function only for the types that fulfill the condition, transforming the weird SFINAE into an equivalent to:

// pseudocode, [] to mark *optional*
template <typename Visitor>
void visit( Visitor & v, Aggregate [const] & s ) {
   v( s.i );
   v( s.d );
}