My question involves how to reuse code, for an algorithm, that is const
unaware (can be used with constant or mutable objects)?
Let's take for example the std::vector
iterators.
There are two iterators classes that share similar methods: std::vector::iterator
and std::vector::const_iterator
.
Both iterators point to slots in the vector or outside the vector (such as std::vector::end()
).
They both have increment and decrement methods.
The primary difference is that the const_iterator
cannot be used for writing to the the vector.
If I were writing the code for iterators, how could I have the iterator
and const_iterator
share methods that are not dependent on the constness of the access operation?
In my present code, I am duplicating the code for for_each
and visit
methods, because of the difference in accesibility. The for_each
loop is the same for
loop the difference being the one applies a const_visitor and the other applies a mutable_visitor.
struct Object;
struct Const_Visitor
{
// Visit function cannot modified the given object.
virtual void visit(const Object& o) = 0;
};
struct Mutable_Visitor
{
// The visit function may modify the given object;
virtual void visit(Object& o) = 0;
};
struct Container
{
const unsigned int LIMIT = 16;
Object obj_container[LIMIT];
// Apply the read-only (constant) visitor
// to each object in the container
void for_each(Const_Visitor& cv) const
{
// Note: this loop management is the same
// as the loop management for the mutable for_each() method.
for (unsigned int i = 0; i < LIMIT; ++i)
{
cv.visit(obj_container[i]);
}
}
// Apply the read/write (mutable) visitor
// to each object in the container.
void for_each(Mutable_Visitor& mv)
{
// Note: this loop management is the same
// as the loop management for the const for_each() method.
for (unsigned int i = 0; i < LIMIT; ++i)
{
mv.visit(obj_container[i]);
}
}
};
In the above example, the mechanics are the same for both for_each
functions. Only the visitor changes. The same slots in the array are being passed to the visit
functions.
This could be changed slightly by using one visitor with two visit
methods, but the fundamental issue still exists.
struct Object;
struct Single_Visitor
{
// Method can't modify the object.
virtual void visit(const Object& o) = 0;
// Method may modify the object.
virtual void visit(Object& o) = 0;
};
struct Container
{
const unsigned int LIMIT = 16;
Object obj_container[LIMIT];
// Apply a visitor to each item in container.
void for_each(Single_Visitor& sv) const
{
for (unsigned int i; i < LIMIT; ++i)
{
// Should call the visit method,
// constant object.
sv.visit(obj_container[i]);
}
}
// Apply a visitor to each item in container.
void for_each(Single_Visitor& sv)
{
for (unsigned int i; i < LIMIT; ++i)
{
// Should call the visit method,
// mutable object.
sv.visit(obj_container[i]);
}
}
};
With a visitor class that has two methods (vs. two separate classes), the container's for_each
methods still have the same mechanics. The looping is the same, just a different method is called.
So, is there a way to have one for_each
loop that calls the appropriate visitor base on const-ness?
It is not possible to have a single non-static member function that can work as either const or non-const. Instead, you can use non-member or static function template with container passed as an argument.
struct Container
{
Object obj_container[LIMIT];
// C can match either `const Container` or `Container`
// V can match either `Const_Visitor` or `Mutable_Visitor`
template<class C, class V>
static void for_each(C& c, V& v) {
for (unsigned int i = 0; i < LIMIT; ++i)
{
v.visit(c.obj_container[i]);
}
}
void for_each(Const_Visitor& cv) const
{
for_each(*this, cv);
}
void for_each(Mutable_Visitor& mv)
{
for_each(*this, mv);
}
};