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.
classObject
{
public:
voidFoo(){}
voidFooConst()const{}
};
classTest
{
private:
unique_ptr<Object> m_pObj;
public:
Test(): m_pObj(make_unique<Object>()){}
voidFoo(){
m_pObj->Foo();
m_pObj->FooConst();
}
voidFooConst()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:
constint* pInt;
intconst* 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
constint*const pInt;
intconst*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:
intControl::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:
constObject*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, so it will be eventually in C++20.
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>
classObject
{
public:
voidFoo(){}
voidFooConst()const{}
};
namespace stdexp = std::experimental;
classTest
{
private:
stdexp::propagate_const<std::unique_ptr<Object>> m_pObj;
public:
Test(): m_pObj(std::make_unique<Object>()){}
voidFoo(){
m_pObj->Foo();
m_pObj->FooConst();
}
voidFooConst()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.