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::vec
C++17 has updated the best answer for this question:
T const & f() const {
return something_complicated();
}
T & f() {
return const_cast<T &>(std::as_const(*this).f());
}
This has the advantages that it:
volatile
by accident, but volatile
is a rare qualifier)If you want to go the full deduction route then that can be accomplished by having a helper function
template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
return value;
}
template<typename T>
void as_mutable(T const &&) = delete;
Now you can't even mess up volatile
, and the usage looks like
decltype(auto) f() const {
return something_complicated();
}
decltype(auto) f() {
return as_mutable(std::as_const(*this).f());
}
Nice question and nice answers. I have another solution, that uses no casts:
class X {
private:
std::vector<Z> v;
template<typename InstanceType>
static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
// massive amounts of code for validating index
// the instance variable has to be used to access class members
return instance.v[i];
}
public:
const Z& get(std::size_t i) const {
return get(*this, i);
}
Z& get(std::size_t i) {
return get(*this, i);
}
};
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.
I came up with a macro that generates pairs of const/non-const functions automatically.
class A
{
int x;
public:
MAYBE_CONST(
CV int &GetX() CV {return x;}
CV int &GetY() CV {return y;}
)
// Equivalent to:
// int &GetX() {return x;}
// int &GetY() {return y;}
// const int &GetX() const {return x;}
// const int &GetY() const {return y;}
};
See the end of the answer for the implementation.
The argument of MAYBE_CONST
is duplicated. In the first copy, CV
is replaced with nothing; and in the second copy it's replaced with const
.
There's no limit on how many times CV
can appear in the macro argument.
There's a slight inconvenience though. If CV
appears inside of parentheses, this pair of parentheses must be prefixed with CV_IN
:
// Doesn't work
MAYBE_CONST( CV int &foo(CV int &); )
// Works, expands to
// int &foo( int &);
// const int &foo(const int &);
MAYBE_CONST( CV int &foo CV_IN(CV int &); )
Implementation:
#define MAYBE_CONST(...) IMPL_CV_maybe_const( (IMPL_CV_null,__VA_ARGS__)() )
#define CV )(IMPL_CV_identity,
#define CV_IN(...) )(IMPL_CV_p_open,)(IMPL_CV_null,__VA_ARGS__)(IMPL_CV_p_close,)(IMPL_CV_null,
#define IMPL_CV_null(...)
#define IMPL_CV_identity(...) __VA_ARGS__
#define IMPL_CV_p_open(...) (
#define IMPL_CV_p_close(...) )
#define IMPL_CV_maybe_const(seq) IMPL_CV_a seq IMPL_CV_const_a seq
#define IMPL_CV_body(cv, m, ...) m(cv) __VA_ARGS__
#define IMPL_CV_a(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_b)
#define IMPL_CV_b(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_a)
#define IMPL_CV_const_a(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_b)
#define IMPL_CV_const_b(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_a)
Pre-C++20 implementation that doesn't support CV_IN
:
#define MAYBE_CONST(...) IMPL_MC( ((__VA_ARGS__)) )
#define CV ))((
#define IMPL_MC(seq) \
IMPL_MC_end(IMPL_MC_a seq) \
IMPL_MC_end(IMPL_MC_const_0 seq)
#define IMPL_MC_identity(...) __VA_ARGS__
#define IMPL_MC_end(...) IMPL_MC_end_(__VA_ARGS__)
#define IMPL_MC_end_(...) __VA_ARGS__##_end
#define IMPL_MC_a(elem) IMPL_MC_identity elem IMPL_MC_b
#define IMPL_MC_b(elem) IMPL_MC_identity elem IMPL_MC_a
#define IMPL_MC_a_end
#define IMPL_MC_b_end
#define IMPL_MC_const_0(elem) IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a(elem) const IMPL_MC_identity elem IMPL_MC_const_b
#define IMPL_MC_const_b(elem) const IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a_end
#define IMPL_MC_const_b_end
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.
A bit more verbose than Meyers, but I might do this:
class X {
private:
// This method MUST NOT be called except from boilerplate accessors.
Z &_getZ(size_t index) const {
return something;
}
// boilerplate accessors
public:
Z &getZ(size_t index) { return _getZ(index); }
const Z &getZ(size_t index) const { return _getZ(index); }
};
The private method has the undesirable property that it returns a non-const Z& for a const instance, which is why it's private. Private methods may break invariants of the external interface (in this case the desired invariant is "a const object cannot be modified via references obtained through it to objects it has-a").
Note that the comments are part of the pattern - _getZ's interface specifies that it is never valid to call it (aside from the accessors, obviously): there's no conceivable benefit to doing so anyway, because it's 1 more character to type and won't result in smaller or faster code. Calling the method is equivalent to calling one of the accessors with a const_cast, and you wouldn't want to do that either. If you're worried about making errors obvious (and that's a fair goal), then call it const_cast_getZ instead of _getZ.
By the way, I appreciate Meyers's solution. I have no philosophical objection to it. Personally, though, I prefer a tiny bit of controlled repetition, and a private method that must only be called in certain tightly-controlled circumstances, over a method that looks like line noise. Pick your poison and stick with it.
[Edit: Kevin has rightly pointed out that _getZ might want to call a further method (say generateZ) which is const-specialised in the same way getZ is. In this case, _getZ would see a const Z& and have to const_cast it before return. That's still safe, since the boilerplate accessor polices everything, but it's not outstandingly obvious that it's safe. Furthermore, if you do that and then later change generateZ to always return const, then you also need to change getZ to always return const, but the compiler won't tell you that you do.
That latter point about the compiler is also true of Meyers's recommended pattern, but the first point about a non-obvious const_cast isn't. So on balance I think that if _getZ turns out to need a const_cast for its return value, then this pattern loses a lot of its value over Meyers's. Since it also suffers disadvantages compared to Meyers's, I think I would switch to his in that situation. Refactoring from one to the other is easy -- it doesn't affect any other valid code in the class, since only invalid code and the boilerplate calls _getZ.]
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):
class X
{
std::vector<Z> vecZ;
public:
const Z& z(size_t index) const
{
// same really-really-really long access
// and checking code as in OP
// ...
return vecZ[index];
}
Z& z(size_t index)
{
// One line. One ugly, ugly line - but just one line!
return const_cast<Z&>( static_cast<const X&>(*this).z(index) );
}
#if 0 // A slightly less-ugly version
Z& Z(size_t index)
{
// Two lines -- one cast. This is slightly less ugly but takes an extra line.
const X& constMe = *this;
return const_cast<Z&>( constMe.z(index) );
}
#endif
};
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.