Jonathan Boccara's blog

The Incredible Const Reference That Isn’t Const

Published July 13, 2018 - 23 Comments

Daily C++

While working on the NamedType library I came across a situation that left me stunned in bewilderment: a const reference that allows modification of the object it refers to. Without a const_cast. Without a mutable. Without anything up the sleeve.

How can this be? And how to enforce the const in that const reference?

A const reference that isn’t const

Here is a description of a situation where this strange reference comes up. Consider the following wrapper class:

template<typename T>
class Wrapper
{
public:
    Wrapper(T const& value) : value_(value) {}
    
    T const& get() const { return value_; }
    
private:
    T value_;
};

This Wrapper class stores a value of type T, and gives access to it via the get() method (independently from that, if you’re wondering if “get” is a good name, you might want to read this detailed discussion). The important part is that the get() method only gives a read-only access to the stored value, because it returns a const reference.

To check that, let’s instantiate the template with int, for instance. Trying to modify the stored value through the get() method fails to compile (try to click the Run button below).


Which is just fine, because we want get() to be read-only.

But let’s now instantiate Wrapper with int&. One use case for this could be to avoid a copy, or keep track of potential modifications of a for example. Now let’s try to modify the stored value through the const reference returned by get():



And if you hit the RUN buttong you will see that… it compiles! And it prints the value modified through the const reference, like nothing special had happened.

Isn’t this baffling? Don’t you feel the reassuring impression of safety provided by const collapsing around us? Who can we trust?

Let’s try to understand what is going on with this reference.

Const reference or reference to const?

The crux of the matter is that our a reference is a const reference, but not a reference to const.

To undersand what that means, let’s decompose step by step what is happening in the template instantiation.

The get() method returns a const T&, with T coming from template T. In our second case, T is int&, so const T& is const (int&) &.

Let’s have a closer look at this const (int&). int& is a reference that refers to an int, and is allowed to modify that int. But const (int&) is a reference int& that is const, meaning that the reference itself cannot be modified.

What does it mean to modify a reference, to begin with? In theory, it would mean making it refer to another object, or not refer to anything. But both those possibilities are illegal in C++: a reference cannot rebind. It refers to the same object during the whole course of it life.

So being const doesn’t say much for a reference, since they always are const, since they cannot rebind. This implies that const (int&) is effectively the same type as int&.

Which leaves us with get() returning (int&) &. And by the rules of references collapsing, this is int&.

So get returns an int&. Its interface says T const&, but it’s in fact a int&. Not really expressive code, is it?

How to make the const reference a reference to const

Now that we’re past the first step of understanding the issue, how can we fix it? That is, how can we make get() return a reference to the stored value that does not allow to modify it?

One way to do this is to explicitly insert a const inside of the reference. We can do this by stripping off the reference, adding a const, and putting the reference back. To strip off the reference, we use std::remove_reference in C++11, or the more convenient std::remove_reference_t in C++14:


As you can see if you click on Run, the expected compilation error now appears when trying to modify the value stored in Wrapper.

I think it is useful to understand what is going on with this reference, because this is code that looks like it’s safe, but is really not, and this is the most dangerous kind.

You can play around with the code in the above playgrounds embedded in the page. And a big thanks to stack overflow user rakete1111 for helping me understand the reasons behind this issue.

You may also like

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed