Search code examples
c++boostassignment-operatorconst-correctnessboost-optional

boost::optional not letting me reassign const value types


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?


Solution

  • (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