It is often quite confusing to C++ newcomers that const member functions are allowed to call non-const methods on objects referenced by the class (either by pointer or refer
One approach is to just not use the pointer directly except through two accessor functions.
class SomeClass
{
private:
class SomeClassImpl;
SomeClassImpl * impl_; // PImpl idiom - don't use me directly!
SomeClassImpl * mutable_impl() { return impl_; }
const SomeClassImpl * impl() const { return impl_; }
public:
void const_method() const
{
//Can't use mutable_impl here.
impl()->const_method();
}
void non_const_method() const
{
//Here I can use mutable_impl
mutable_impl()->non_const_method();
}
};
As @Alf P. Steinbach noted, you oversaw the fact that copying your pointer would yield a non-const object pointing to the same underlying object. Pimpl
(below) nicely circumvent the issue by performing a deep-copy, unique_ptr
circumvents it by being non-copyable. It is much easier, of course, if the pointee is owned by a single entity.
Boost.Optional propagates const-ness, however it's not exactly a pointer (though it models the OptionalPointee concept). I know of no such other library.
I would favor that they provide it by default. Adding another template parameter (traits class I guess) does not seem worth the trouble. However that would radically change the syntax from a classic pointer, so I am not sure that people would be ready to embrace it.
Code of the Pimpl
class
template <class T>
class Pimpl
{
public:
/**
* Types
*/
typedef T value;
typedef const T const_value;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
/**
* Gang of Four
*/
Pimpl() : _value(new T()) {}
explicit Pimpl(const_reference v) : _value(new T(v)) {}
Pimpl(const Pimpl& rhs) : _value(new T(*(rhs._value))) {}
Pimpl& operator=(const Pimpl& rhs)
{
Pimpl tmp(rhs);
swap(tmp);
return *this;
} // operator=
~Pimpl() { boost::checked_delete(_value); }
void swap(Pimpl& rhs)
{
pointer temp(rhs._value);
rhs._value = _value;
_value = temp;
} // swap
/**
* Data access
*/
pointer get() { return _value; }
const_pointer get() const { return _value; }
reference operator*() { return *_value; }
const_reference operator*() const { return *_value; }
pointer operator->() { return _value; }
const_pointer operator->() const { return _value; }
private:
pointer _value;
}; // class Pimpl<T>
// Swap
template <class T>
void swap(Pimpl<T>& lhs, Pimpl<T>& rhs) { lhs.swap(rhs); }
// Not to be used with pointers or references
template <class T> class Pimpl<T*> {};
template <class T> class Pimpl<T&> {};
For the record, I just found out that the Loki library does provide a const propagating pointer (ConstPropPtr<T>). It looks just like the one in the question, except that it also deletes the wrapped pointer in its destructor, and it is used to implement a Pimpl class similar to the one proposed by @Matthieu (but not copyable).
If you think it should "propagate" const-ness, then it means you don't really believe it is a pointer (or reference), but you believe it is a container: if the value is constant when the object is constant, it's because the object contains the value.
So copying the object copies the value, at least logically (CoW).
If you insist that it is a pointer/reference IOW that you can copy the object while sharing the contained value, then you have an unsound (contradicting) interface.
Conclusion: make up your mind. It is either a container or a pointer.
A pointer does not propagate const-ness, by definition.