I've been teaching a C++ programming class for many years now and one of the trickiest things to explain to students is const overloading. I commonly use the example of a vector-like class开发者_开发知识库 and its operator[]
function:
template <typename T> class Vector {
public:
T& operator[] (size_t index);
const T& operator[] (size_t index) const;
};
I have little to no trouble explaining why it is that two versions of the operator[]
function are needed, but in trying to explain how to unify the two implementations together I often find myself wasting a lot of time with language arcana. The problem is that the only good, reliable way that I know how to implement one of these functions in terms of the other is with the const_cast
/static_cast
trick:
template <typename T> const T& Vector<T>::operator[] (size_t index) const {
/* ... your implementation here ... */
}
template <typename T> T& Vector<T>::operator[] (size_t index) {
return const_cast<T&>(static_cast<const Vector&>(*this)[index]);
}
The problem with this setup is that it's extremely tricky to explain and not at all intuitively obvious. When you explain it as "cast to const, then call the const version, then strip off constness" it's a little easier to understand, but the actual syntax is frightening,. Explaining what const_cast
is, why it's appropriate here, and why it's almost universally inappropriate elsewhere usually takes me five to ten minutes of lecture time, and making sense of this whole expression often requires more effort than the difference between const T*
and T* const
. I feel that students need to know about const-overloading and how to do it without needlessly duplicating the code in the two functions, but this trick seems a bit excessive in an introductory C++ programming course.
My question is this - is there a simpler way to implement const
-overloaded functions in terms of one another? Or is there a simpler way of explaining this existing trick to students?
I usually consider this to be a language restriction, and advise people that -- unless they really know what they're doing -- they should just reimplement. In the vast majority of cases, these functions are simple one-line getters, so there's no pain.
In your capacity of teaching C++ I would feel even more strongly about this approach.
How about simply breaking it down into smaller steps?
const Vector<T>& const_this = *this;
const T& const_elem = const_this[index];
T& mutable_elem = const_cast<T&>(const_elem);
return mutable_elem;
You can even eliminate the static_cast
this way, although you could leave it in if you think it would be clearer.
It is quite a weird option, but this can be done using a static template helper like this
// template parameters can be const or non-const
template<class Ret, class C>
static Ret& get(C* p, size_t index) { /* common code here like p->array[index] */ }
Then you can write
const T& operator[](size_t index) const { return get<const T>(this, index); }
T& operator[](size_t index) { return get<T>(this, index); }
This trick avoids casts (!) and double implementation, but, again, it looks weird to me :)
And a small remark about your snippet code, won't const_cast
be enough instead of that static_cast
, or am I missing something?
You can remove one cast by using a private method:
It adds a method but makes the casting less complex:
template <typename T>
class Vector
{
public:
T const& operator[](size_t i) const { return getValue(i);}
T& operator[](size_t i) { return const_cast<T&>(getValue(i));}
private:
T const& getValue(size_t i) const { return /* STUFF */;}
};
In my opinion this is just silly. You're forcing one to be implemented in terms of the other simply for the sake of doing so, not because the resulting code is easier to maintain or understand. The reason your students are confused is probably because they SHOULD be.
Not every principle should be taken to the exclusive extreme. Sometimes being redundant is simply better.
Instead of making one version call the other, you could let both call a helper function which finds the right item. You already seem to be introducing templates, so letting the helper function be a template as well should work and avoid the code duplication, and work for both const and non-const without any const_cast
s.
Alternately, you could use local variables to help break the expression into manageable pieces (with comments for each).
const ClassType& cthis = *this; // look, no explicit cast needed here :)
const T& elem = cthis[index]; // delegate to const version
return const_cast<T&>(elem); // ok to strip off the const, since we added it in the first place
If the implementation will the exact same code (for this 'vector' class example or whatever), then why not have the non-const version call the const version, rather than the other way around. If for some reason the code has to modify a member, then maybe there shouldn't really be a const version (ignoring the whole mutable thing...).
精彩评论