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?
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.
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.
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.
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 .
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 ); }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With