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
It's surprising to me that there are so many different answers, yet almost all rely on heavy template magic. Templates are powerful, but sometimes macros beat them in conciseness. Maximum versatility is often achieved by combining both.
I wrote a macro FROM_CONST_OVERLOAD()
which can be placed in the non-const function to invoke the const function.
class MyClass
{
private:
std::vector data = {"str", "x"};
public:
// Works for references
const std::string& GetRef(std::size_t index) const
{
return data[index];
}
std::string& GetRef(std::size_t index)
{
return FROM_CONST_OVERLOAD( GetRef(index) );
}
// Works for pointers
const std::string* GetPtr(std::size_t index) const
{
return &data[index];
}
std::string* GetPtr(std::size_t index)
{
return FROM_CONST_OVERLOAD( GetPtr(index) );
}
};
template
T& WithoutConst(const T& ref)
{
return const_cast(ref);
}
template
T* WithoutConst(const T* ptr)
{
return const_cast(ptr);
}
template
const T* WithConst(T* ptr)
{
return ptr;
}
#define FROM_CONST_OVERLOAD(FunctionCall) \
WithoutConst(WithConst(this)->FunctionCall)
As posted in many answers, the typical pattern to avoid code duplication in a non-const member function is this:
return const_cast( static_cast(this)->Method(args) );
A lot of this boilerplate can be avoided using type inference. First, const_cast
can be encapsulated in WithoutConst()
, which infers the type of its argument and removes the const-qualifier. Second, a similar approach can be used in WithConst()
to const-qualify the this
pointer, which enables calling the const-overloaded method.
The rest is a simple macro that prefixes the call with the correctly qualified this->
and removes const from the result. Since the expression used in the macro is almost always a simple function call with 1:1 forwarded arguments, drawbacks of macros such as multiple evaluation do not kick in. The ellipsis and __VA_ARGS__
could also be used, but should not be needed because commas (as argument separators) occur within parentheses.
This approach has several benefits:
FROM_CONST_OVERLOAD( )
const_iterator
, std::shared_ptr
, etc.). For this, simply overload WithoutConst()
for the corresponding types.Limitations: this solution is optimized for scenarios where the non-const overload is doing exactly the same as the const overload, so that arguments can be forwarded 1:1. If your logic differs and you are not calling the const version via this->Method(args)
, you may consider other approaches.