Let's say I have the following class X
where I want to return access to an internal member:
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
Z& Z(size_t index)
{
// massive amounts of code for validating index
Z& ret = vecZ[index];
// even more code for determining that the Z instance
// at index is *exactly* the right sort of Z (a process
// which involves calculating leap years in which
// religious holidays fall on Tuesdays for
// the next thousand years or so)
return ret;
}
const Z& Z(size_t index) const
{
// identical to non-const X::Z(), except printed in
// a lighter shade of gray since
// we're running low on toner by this point
}
};
The two member functions X::Z()
and X::Z() const
have identical code inside the braces. This is duplicate code and can cause maintenance problems for long functions with complex logic.
Is there a way to avoid this code duplication?
For a detailed explanation, please see the heading "Avoid Duplication in
const
and Non-const
Member Function," on p. 23, in Item 3 "Useconst
whenever possible," in Effective C++, 3d ed by Scott Meyers, ISBN-13: 9780321334879.Here's Meyers' solution (simplified):
The two casts and function call may be ugly but it's correct. Meyers has a thorough explanation why.
Didn't find what I was looking for, so I rolled a couple of my own...
This one is a little wordy, but has the advantage of handling many overloaded methods of the same name (and return type) all at once:
If you have only one
const
method per name, but still plenty of methods to duplicate, then you might prefer this:Unfortunately this breaks down as soon as you start overloading the name (the function pointer argument's argument list seems to be unresolved at that point, so it can't find a match for the function argument). Although you can template your way out of that, too:
But reference arguments to the
const
method fail to match against the apparently by-value arguments to the template and it breaks.Not sure why.Here's why.Yes, it is possible to avoid the code duplication. You need to use the const member function to have the logic and have the non-const member function call the const member function and re-cast the return value to a non-const reference (or pointer if the functions returns a pointer):
NOTE: It is important that you do NOT put the logic in the non-const function and have the const-function call the non-const function -- it may result in undefined behavior. The reason is that a constant class instance gets cast as a non-constant instance. The non-const member function may accidentally modify the class, which the C++ standard states will result in undefined behavior.
This DDJ article shows a way using template specialization that doesn't require you to use const_cast. For such a simple function it really isn't needed though.
boost::any_cast (at one point, it doesn't any more) uses a const_cast from the const version calling the non-const version to avoid duplication. You can't impose const semantics on the non-const version though so you have to be very careful with that.
In the end some code duplication is okay as long as the two snippets are directly on top of each other.
Nice question and nice answers. I have another solution, that uses no casts:
However, it has the ugliness of requiring a static member and the need of using the
instance
variable inside it.I did not consider all the possible (negative) implications of this solution. Please let me know if any.
Typically, the member functions for which you need const and non-const versions are getters and setters. Most of the time they are one-liners so code duplication is not an issue.