Suppose we a class Mutable
which has a non-const member function, void Mutable::mutate()
, and a class having a data member of type Mutable
. Calls to mutate
on said member in a function that is declared const is then prohibited. In code:
class Mutable
{
public:
void mutate();
};
class UseMutable
{
Mutable m;
public:
int compute() const
{
// does not compile
m.mutate();
return 42;
}
};
If the type of m
were a reference or a (shared) pointer to Mutable
instead, we lose the compile time protection:
class UseMutablePtr
{
Mutable* m;
public:
int compute() const
{
// compiles =(
m->mutate();
return 42;
}
};
or
class UseMutableRef
{
Mutable& m;
public:
int compute() const
{
// compiles =(
m.mutate();
return 42;
}
};
How does one prevent this behaviour?
If only const
member functions of Mutable
are to be called, one can use const Mutable&
or const Mutable*
(or the corresponding std::..._ptr<const Mutable>
, but what if one wants to call both const
and non-const
member functions of Mutable
?
For pointers one could use a custom smart pointer which overloads the arrow and dereferencing operators as
template<class T>
class CustomSmartPtr
{
T* ptr;
public:
T* operator->() { return ptr; }
const T* operator->() const { return ptr; }
T& operator*() { return *ptr; }
const T& operator*() const { return *ptr; }
};
since the smart pointers in the standard libary behave like normal pointers in this regard. For a replacement of unique_ptr
this is not that big of a hassle, but for shared_ptr
quite some effort is required.
For references the only thing that comes to mind is to reimplement std::reference_wrapper
with the desired behaviour, i.e. to have conversion operators with the desired signature. This is again not too bad.
Are there "cheaper" ways to enforce the desired behaviour, or is there maybe some tooling that would at least detect violations?
Out of curiosity: why is this the default?
I have not yet come across an instance where this default behaviour is really desired, but have seen subtle bug that could have been prevented. Is this behaviour at least partially due to interoperability with C?