It seems to me there should be four variants of boost::optional
optional<Foo>
=> holds a mutable Foo and can be reassigned after initialization
optional<Foo const> const
=> holds a const Foo and can't be reassigned after initialization
optional<Foo> const
=> (should?) hold a mutable Foo but can't be reassigned after initialization
optional<Foo const>
=> (should?) hold a const Foo and can be reassigned after initialization
The first 2 cases work as expected. But the optional<Foo> const
dereferences to a const Foo, and the optional<Foo const>
doesn't allow reassignment after initialization (as touched upon in this question).
The reassignment of the const value types is specifically what I ran into, and the error is:
/usr/include/boost/optional/optional.hpp:486: error: passing ‘const Foo’ as ‘this’ argument of ‘Foo& Foo::operator=(const Foo&)’ discards qualifiers [-fpermissive]
And it happens here:
void assign_value(argument_type val,is_not_reference_tag) { get_impl() = val; }
After construction, the implementation uses the assignment operator for the type you parameterized the optional with. It obviously doesn't want a left-hand operand which is a const value. But why shouldn't you be able to reset a non-const optional to a new const value, such as in this case:
optional<Foo const> theFoo (maybeGetFoo());
while (someCondition) {
// do some work involving calling some methods on theFoo
// ...but they should only be const ones
theFoo = maybeGetFoo();
}
Some Questions:
Am I right that wanting this is conceptually fine, and not being able to do it is just a fluke in the implementation?
If I don't edit the boost sources, what would be a clean way to implement logic like in the loop above without scrapping boost::optional altogether?
If this does make sense and I were to edit the boost::optional source (which I've already had to do to make it support movable types, though I suspect they'll be doing that themselves soon) then what minimally invasive changes might do the trick?
So basically the problem seems to be related to this note in the documentation for optional& optional<T (not a ref)>::operator= ( T const& rhs )
:
Notes: If *this was initialized, T's assignment operator is used, otherwise, its copy-constructor is used.
That is, suppose you have boost::optional<const Foo> theFoo;
. Since a default constructed boost::optional<>
is empty, the statement:
theFoo=defaultFoo;
should mean "copy construct defaultFoo
into theFoo
s internal storage." Since there's nothing already in that internal storage, this makes sense, even if the internal storage is supposed to house a const Foo
. Once finished, theFoo
will not be empty.
Once theFoo
contains a value, the statement
theFoo=defaultFoo;
should mean "assign defaultFoo
into the object in theFoo
s internal storage." But theFoo
s internal storage isn't assignable (as it is const
), and so this should raise a (compile time?) error.
Unfortunately, you'll notice the last two statements are identical, but conceptually require different compile time behavior. There's nothing to let the compiler tell the difference between the two, though.
Particularly in the scenario you're describing, it might make more sense to define boost::optional<...>
's assignment operator to instead have the semantics:
If *this was initialized, its current contents are first destroyed. Then
T
's copy-constructor is used.
After all, it's entirely possible to invoke T
's assignment operator if that's what you really want to do, by saying *theFoo = rhs
.
(1) One's opinion on what the behavior "should" be depends on whether optionals are "a container for zero or one objects of an arbitrary type" or "a thin proxy for a type, with an added feature". The existing code uses the latter idea, and by doing so, it removes half of the "four different behaviors" in the list. This reduces the complexity, and keeps you from unintentionally introducing inefficient usages.
(2) For any Foo
type whose values are copyable, one can easily switch between mutable and immutable optionals by making a new one. So in the given case, you'd get it as mutable briefly and then copy it into an immutable value.
optional<Foo> theMutableFoo (maybeGetFoo());
while (someCondition) {
optional<Foo const> theFoo (theMutableFoo);
// do some work involving calling some methods on theFoo
// ...but they should only be const ones
// ...therefore, just don't use theMutableFoo in here!
theMutableFoo = maybeGetFoo();
}
Given the model that it's a "thin proxy" for a type, this is exactly the same kind of thing you would have to do if the type were not wrapped in an optional. An ordinary const value type needs the same treatment in such situations.
(3) One would have to follow up on the information given by @Andrzej to find out. But such an implementation change would probably not perform better than creating a new optional every time (as in the loop above). It's probably best to accept the existing design.
Sources: @R.MartinhoFernandez, @KerrekSB
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With