Mutable Access
A matter of const
optional provides – naturally – access to the value it stores. We have
multiple ways to do so, but they all lack one capability.
Let’s say, we store a std::vector<int> in our optional and we want to push a value in it.
optional<std::vector<int>> vs;
vs->push_back(5);This code won’t compile. The error message will look basically like this:
passing ‘const std::vector<int>’ as ‘this’ argument discards qualifiersWe have seen such an error before. The issue is, that the -> operator returns a
pointer to a const T – in this case a const std::vector<int>. We then try to call push_back on this object, which
is rejected, because push_back is not a const member
function.
This means that, if we want to be able to write code as done above, we will need to provide mutable access to an optional’s value. So we need to provide non-const versions of
value()- the
*operator and - the
->operator.
Adding the tests
Creating a suitable test is pretty straight forward:
TEST_CASE("An non-const optional's value can be mutated.") {
struct A {
unsigned int x;
};
A anyValue = {10};
optional<A> x(anyValue);
A anyOtherValue = {5};
SECTION("value()") {
x.value() = anyOtherValue;
}
SECTION("*-operator") {
*x = anyOtherValue;
}
SECTION("->-operator") {
x->x = anyOtherValue.x;
}
REQUIRE(x->x == anyOtherValue.x);
}catch’s SECTIONs come in
quite handy here. Each of the SECTIONs we introduced here are executed independently although they share the same
setup and assertion code.
Adding mutable access
The implementation is rather simple, too: we basically can copy the const versions of the accessor member functions
and operators and remove the const qualifiers from the signature.
template <typename T>
class optional {
public:
//...
const T& value() const {
if (not mHasValue) {
throw bad_optional_access();
}
return mValue;
}
T& value() {
if (not mHasValue) {
throw bad_optional_access();
}
return mValue;
}
const T& operator*() const {
return mValue;
}
T& operator*() {
return mValue;
}
const T* operator->() const {
return &mValue;
}
T* operator->() {
return &mValue;
}
//...
};This is enough to make the new test pass. It is not that nice though that we have to
repeat ourselfs that much. For the operators it is not that bad
after all, but for value() it is just ridiculous. Luckily we can help ourselfs by introducing a little helper function.
template <typename T>
class optional {
public:
//...
const T& value() const {
throwInCaseOfBadAccess();
return mValue;
}
T& value() {
throwInCaseOfBadAccess();
return mValue;
}
//...
private:
//...
void throwInCaseOfBadAccess() const {
if (not mHasValue) {
throw bad_optional_access();
}
}
};Conclusion
With these changes our optional can now finally be mutated via value(), * and ->. This is great, as it allows
for many more use cases. Additionally it should not be on us – library implementers – to decide whether is should be
allowed for a used to change the value of an optional of not. With this solution, the user can make this decision on
it’s own simply but putting a const on the respective optional or not.