As user @Nawaz has pointed out in their answer, it is better to const_cast
the other way, which has less potential for making a mistake. This can be made slightly easier with std::as_const
:
int& get() { return const_cast<int&>(std::as_const(*this).get()); }
However, many style guides ban const_cast
(e.g. CppCoreGuidelines) for such purposes because it is difficult to tell whether what you're doing is correct at first glance. Consider the following alternatives.
Static member function template (C++98 - C++20)
private:
// C++20 abbreviated function template.
// In C++17 and prior, use template <typename Self>
// Implementable in C++98 (see comments), but much more verbose.
static auto& get(auto &self) { return self.m_someData; }
public:
int& get() { return get(*this); }
const int& get() const { return get(*this); }
This solution works because *this
will be either A
or const A
depending on non-static member function is calling the static member function template.
Many developers prefer this, because there is no const_cast
at all, and so it is trivial to verify that this code is const-correct. However, get
being a template may increase code size.
With this C++23 feature, we can accept this
as a function parameter and handle all combinations of cv-qualifiers and reference qualifiers in one function.
This is similar to the first solution, however, we only have to write a single function.
// returns int&, const int&, volatile int&, or const volatile int&
auto& get(this auto &&self) { return self.m_someData; }
You could extend this further, so that when you have an A
rvalue, you return int&&
. For an int
, this would be useless, but it may make sense for other types of members:
// returns int&, const int&, volatile int&, const volatile int&,
// int&&, const int&&, volatile int&&, or const volatile int&&
template <typename Self>
auto&& get(this Self &&self) { return std::forward<Self>(self).m_someData; }
const
. And with the const overload calling the non-const one, you at least save oneconst_cast
. – Indeterminism