An immutable optional
Immutability
Since at least functional programming gained more and more popularity among programmers outside the academic sphere,
immutable data is considered a good thing. C++ supports the immutability of variables or objects in form of the const
key word since its early days (see The Design and Evolution of C++ for reference): if we
declare a variable with this keyword, we may not alter it’s value after initialization.
Making as many variables and objects immutable immutable is considered a best practice in the C++ community:
- Item 21 of Scott Meyers’ Effective C++ recommends to “Use const whenever possible.”
- Item 15 of C++ Coding Standards: 101 Rules, Guidelines, and Best Practices by Herb Sutter and Andrei Alexandrescu advises us to “Use const proactively.”
- Even John Carmack seems to like ‘const’ (have a look at his comment under this articel).
- Consequentially the C++ Core Guidelines have a whole section on Constants and immutablility containting Con.1 which states: “by default, make objects immutable”.
A few of the most frequently mentioned reasons for using const
as much as possible are:
- Ease of reasoning about a piece of code: we don’t need to keep track of state changes of variables if there aren’t any.
- The compiler enforces constants to be unchanged: this may turn some programming errors into compiler errors.
- Immutability can be very helpful in multithreaded contexts.
- Striving for constness helps to avoid the “Initialize Then Modify” Antipattern.
These arguments should be conclusive enough to convince us that immutability is a good thing and that therefore our
optional should support immutability. We shoudn’t make the members of our optional constant though! If we would do that,
we would use /assignability/. As soon as one member of a class/struct is const
, assignment on this object is
disallowed, because this would require to reassign a constant, which is impossible. As our optional should keep being a
regular type, we should refrain from this idea
(C.12 also advises us to not to make data
members const
).
But the least we can do is making sure that our optional may be declared and used as a constant.
Constant optionals in the tests
So the first thing, we should do, is to use constant optionals in our tests; for example in these two cases:
But if we try to build our tests now, we will get a compiler error: passing ‘const optional_unsigned_int’ as ‘this’
argument discards qualifiers
.
Member functions and constants
In order to understand this, we should take a step back and have a look on a reduced version of our optional.
For us as a reader, it is pretty clear, that none of the member functions has_value()
and value()
alters the classes
members, so for us there may be no particular reason, why it shouldn’t be possible to call these functions on a constant
optional.
The reason is, that the compiler basically treats theses member function as if the we something like this:
Every non-static member function takes a pointer to the object, the function is called on, as the this
pointer.
If these member functions are declared as we did above, the this
pointer points to a non-const/mutable object.
But we can not pass an object which has been declared to be const
to a function, which takes a pointer to a non-const
object. This is, because these function may alter the object via this pointer, which is not allowed for constants.
With this knowledge, the compiler error we encountered above starts to make sense. We actually passed a const
optional_unsigned_int
as this
which is – in this case – a pointer to a non-const optional_unsigned_int
and this
discards the const
qualifier, which isn’t allowed.
So what can we do about this issue? We need to make the this
pointer a pointer to a constant. This is done by adding
const
to the function definitions.
This const
applies to the this
pointer implicitly passed to the member function: optional_unsigned_int * this
becomes to const optional_unsigned_int * this
. With this addition, we can now call these two member functions on a
constant optional, we implies that out tests are compiling again without any errors now.
With this addition, out optional also complies to
Con.2 of the C++ Core Guidelines: “by
default, make member functions const
”. We also understand now, why this rule makes sense: it enables the said member
functions to be called on const
objects.
Conclusion
Our optional is now pretty much usable: it protects it’s invariant, is regular and is const correct. As it is, it
would be pretty much ready for everyday use. Now we can finally start to generalize it in order to get closer to the
actual std::optional
implementation.