Last Update:
How to propagate const on a pointer data member?
Table of Contents
Inside const
methods all member pointers become constant pointers.
However sometimes it would be more practical to have constant pointers
to constant objects.
So how can we propagate such constness?
The problem
Let’s discuss a simple class that keeps a pointer to another class. This member field might be an observing (raw) pointer, or some smart pointer.
class Object
{
public:
void Foo() { }
void FooConst() const { }
};
class Test
{
private:
unique_ptr<Object> m_pObj;
public:
Test() : m_pObj(make_unique<Object>()) { }
void Foo() {
m_pObj->Foo();
m_pObj->FooConst();
}
void FooConst() const {
m_pObj->Foo();
m_pObj->FooConst();
}
};
We have two methods Test::Foo
and Test::FooConst
that calls all
methods (const and non-const) of our m_pObj
pointer.
Can this compile?
Of course!
So what’s the problem here?
Have a look:
Test::FooConst
is a const method, so you cannot modify members of the
object. In other words they become const. You can also see it as this
pointer inside such method becomes const Test *
.
In the case of m_pObj
it means you cannot change the value of it
(change its address), but there’s nothing wrong with changing value that
it’s pointing to. It also means that if such object is a class, you can
safely call its non const methods.
Just for the reference:
// value being pointed cannot be changed:
const int* pInt;
int const* pInt; // equivalent form
// address of the pointer cannot be changed,
// but the value being pointed can be
int* const pInt;
// both value and the address of the
// pointer cannot be changed
const int* const pInt;
int const* const pInt; // equivalent form
m_pObj
becomes Object* const
but it would be far more useful to have
Object const* const
.
In short: we’d like to propagate const on member pointers.
Small examples
Are there any practical examples?
One example might be with Controls:
If a Control
class contains an EditBox
(via a pointer) and you call:
int Control::ReadValue() const
{
return pEditBox->GetValue();
}
auto val = myControl.ReadValue();
It would be great if inside Control::ReadValues
(which is const) you
could only call const methods of your member controls (stored as
pointers).
And another example: the pimpl
pattern.
Pimpl divides class and moves private section to a separate class. Without const propagation that private impl can safely call non-const methods from const methods of the main class. So such design might be fragile and become a problem at some point. Read more in my recent posts: here and here.
What’s more there’s also a notion that a const method should be thread safe. But since you can safely call non const methods of your member pointers that thread-safety might be tricky to guarantee.
Ok, so how to achieve such const propagation through layers of method calls?
Wrappers
One of the easiest method is to have some wrapper around the pointer.
I’ve found such technique while I was researching for pimpl
(have a
look here: The Pimpl Pattern - what you should
know).
You can write a wrapper method:
const Object* PObject() const { return m_pObj; }
Object* PObject() { return m_pObj; }
And in every place - especially in const
method(s) of the Test
class
- you have to use
PObject
accessor. That works, but might require consistency and discipline.
Another way is to use some wrapper type. One of such helpers is suggested in the article Pimp My Pimpl — Reloaded | -Wmarc.
In the StackOverflow question: Propagate constness to data pointed by member variables I’ve also found that Loki library has something like: Loki::ConstPropPtr\
propagate_const
propagate_const
is currently in TS of library fundamentals TS v2:
C++ standard libraries extensions, version
2.
And is the wrapper that we need:
From propagate_const @cppreference.com:
std::experimental::propagate_const
is a const-propagating wrapper for pointers and pointer-like objects. It treats the wrapped pointer as a pointer to const when accessed through a const access path, hence the name.
As far as I understand this TS is already published (even before C++17). Still not all features were merged into C++17… so not sure if that’s reach C++20. See this r/cpp comment.
It’s already available in
- GCC (libstdc++) - Implementation Status, libstdc++
- Clang (libc++) - code review std::experimental::propagate_const from LFTS v2
- MSVC: not yet
Here’s the paper:
N4388 - A Proposal to Add a Const-Propagating Wrapper to the Standard Library
The authors even suggest changing the meaning of the keyword const… or a new keyword :)
Given absolute freedom we would propose changing the const keyword to propagate const-ness.
But of course
That would be impractical, however, as it would break existing code and change behaviour in potentially undesirable ways
So that’s why we have a separate wrapper :)
We can rewrite the example like this:
#include <experimental/propagate_const>
class Object
{
public:
void Foo() { }
void FooConst() const { }
};
namespace stdexp = std::experimental;
class Test
{
private:
stdexp::propagate_const<std::unique_ptr<Object>> m_pObj;
public:
Test() : m_pObj(std::make_unique<Object>()) { }
void Foo() {
m_pObj->Foo();
m_pObj->FooConst();
}
void FooConst() const {
//m_pObj->Foo(); // cannot call now!
m_pObj->FooConst();
}
};
propagate_const
is move constructible and move assignable, but not
copy constructable or copy assignable.
Playground
As usual you can play with the code using a live sample:
Summary
Special thanks to author - iloveportalz0r - who
commented
on my previous article about
pimpl and suggested using
popagate_const
! I haven’t seen this wrapper type before, so it’s
always great to learn something new and useful.
All in all I think it’s worth to know about shallow const problem. So if
you care about const correctness in your system (and you should!) then
propagate_const
(or any other wrapper or technique) is very important
tool in your pocket.
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: