Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 220
paperjam Avatar asked Oct 17 '11 09:10

paperjam


People also ask

How is it possible to have both const and non const version of a function?

There are legitimate uses of having two member functions with the same name with one const and the other not, such as the begin and end iterator functions, which return non-const iterators on non-const objects, and const iterators on const objects, but if it's casting from const to do something, it smells like fish.

Can a const function call a non const function?

const member functions may be invoked for const and non-const objects. non-const member functions can only be invoked for non-const objects. If a non-const member function is invoked on a const object, it is a compiler error.

Can a const function return a non const reference?

If the thing you are returning by reference is logically part of your this object, independent of whether it is physically embedded within your this object, then a const method needs to return by const reference or by value, but not by non-const reference.

Can a const reference be bound to a non const object?

No. A reference is simply an alias for an existing object. const is enforced by the compiler; it simply checks that you don't attempt to modify the object through the reference r .


Video Answer


2 Answers

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 ); } 
like image 100
David Rodríguez - dribeas Avatar answered Sep 18 '22 22:09

David Rodríguez - dribeas


struct Aggregate {     int i;     double d;      template <class Visitor>     void operator()(Visitor &v)     {         visit(this, v);     }     template <class Visitor>     void operator()(Visitor &v) const     {         visit(this, v);     }   private:     template<typename ThisType, typename Visitor>     static void visit(ThisType *self, Visitor &v) {         v(self->i);         v(self->d);     } }; 

OK, so there's still some boilerplate, but no duplication of the code that depends on the actual members of the Aggregate. And unlike the const_cast approach advocated by (e.g.) Scott Meyers to avoid duplication in getters, the compiler will ensure the const-correctness of both public functions.

like image 27
Steve Jessop Avatar answered Sep 18 '22 22:09

Steve Jessop