8

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?

Community
  • 1
  • 1
  • 1
    Consider `Foo const* p = /*...*/ ; *p = Foo();`. Now replace the pointer with optional: `optional p; *p = Foo();`. Same behaviour. – R. Martinho Fernandes Jul 12 '12 at 19:40
  • 1
    A `boost::optional` is essentially a drop-in replacement for a `T`. If you have something const, then you don't reassign it. If you want to reassign it, don't make it const. – Kerrek SB Jul 12 '12 at 19:41
  • Though it's not exactly what's asked, maybe the question is more more toward `Foo const *p = gen(...);` `p=gen(...);`. This should work fine for `Foo const *`, but doesn't work for `optional`. – Managu Jul 12 '12 at 19:45
  • @Managu because optional exists to provide value semantics, while the example with the pointer is dealing with reference semantics. – R. Martinho Fernandes Jul 12 '12 at 19:46
  • 2
    Both `optional` and `optional` both make a copy for the user. Why do you want to restrict the constness of their copy? It seems to be the same a returning a constant copy of an object to the user from a function. – Dave S Jul 12 '12 at 19:47
  • It's worth noting that you I can't seem to do the same type of thing with `std::vector` either: http://ideone.com/sATY6 – Managu Jul 12 '12 at 19:57
  • @Managu The reasons for that are different...apparently they came close to supporting it in C++11, and it may be made to work if you tinker...see [comment on this question](http://stackoverflow.com/questions/6954906/does-c0x-allow-vectorconst-t#comment8294604_6955332) – HostileFork says dont trust SE Jul 12 '12 at 20:02
  • @RMartinhoFernandes: The question seems to be about mutability instead of copying, though. Stated differently (and using "container" loosely): Suppose I have a container that I want to hold immutable values. Must the container be immutable? All evidence suggests this is so for `boost::optional` as well as the standard containers. – Managu Jul 12 '12 at 20:02

3 Answers3

3

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 theFoos 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 theFoos internal storage." But theFoos 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.

Managu
  • 8,849
  • 2
  • 30
  • 36
  • Classes and templates are very general, and what I believe @KerrekSB was getting at above is that not everything is a "container". `boost::optional` might on the surface seem like one, but it's designed to add an "existence bit" onto a type...but otherwise act like that type. By that logic I suppose it fits into a category of design pattern objects maybe more like a ["proxy"](http://en.wikipedia.org/wiki/Proxy_pattern) or "augmentator" or something (these terms are all a bit fuzzy)...but the key being that it's not a "container". :-/ – HostileFork says dont trust SE Jul 12 '12 at 20:35
  • 1
    @HostileFork: I disagree. A `boost::optional` acts nothing like a `T`. If you want to get at/act on the underlying `T`, you usually have to dereference: `boost::optional opt_i(6);` / `std::cout<<*opt_i<` is justly considered a container for 0 or 1 `T`s. – Managu Jul 12 '12 at 20:40
  • I see your side of course (that's why I wrote the question! :-P) But beyond the added difficulty of reading, think about writing...if your function's return type is an `optional`, you can just return a `T` value. So it's a bit asymmetrical in this regard. – HostileFork says dont trust SE Jul 12 '12 at 20:48
  • +1 on the research in any case...although it's more of an elaboration on the problem than an answer. :) – HostileFork says dont trust SE Jul 12 '12 at 23:05
  • I wound up [self-answering](http://stackoverflow.com/a/11479925/211160) this one. Note my update to the question about how dereferencing a `const optional` gets you a const Foo. I've been pretty much converted to the side of believing that this is the right idea... – HostileFork says dont trust SE Jul 14 '12 at 21:02
2

To answer your three questions:

  1. The implementation follows the design philosophy that optional's assignment uses T's assignment; and in this sense, the implementation is fine.
  2. optional was designed with possible extensions in mind. Optional is only an interface for the underlying class optional_base. Instead of using optional you can derive your own class from optional_base. optional_base has a protected member construct, which does almost what you need. You will need a new member, say reset(T) that firsts clears the optional_base and then calls construct().
  3. Alternatively, you can add member reset(T) to optional. This would be the least intrusive change.

You could also try the reference implementation of optional from this proposal.

Andrzej
  • 5,027
  • 27
  • 36
  • I tried out the reference implementation, thanks for the link...it seemed to expect T to be default-constructible in order to work which is sort of a further step back (!) I'm interested in this idea of extending `optional_base` although now I'm wondering if the problem could be addressed with something like "reference_wrapper", except it would be something like "value_wrapper"...with the same sort of intention. ? – HostileFork says dont trust SE Jul 12 '12 at 23:08
  • @HostileFork: Whatever you mean by "value_wrapper", I am pretty sure it will be significantly different from reference_wrapper: the latter is always implemented as a single pointer. In your case you will probably need an aligned storage (of size and alignment of T) and a way to manually control construction ad destruction of the object(s). This is where optional_base will help you. – Andrzej Jul 13 '12 at 07:19
  • I wound up [self-answering](http://stackoverflow.com/a/11479925/211160) this one. Note my update to the question about how dereferencing a `const optional` gets you a const Foo. I've been pretty much converted to the side of believing that this is the right idea... – HostileFork says dont trust SE Jul 14 '12 at 21:03
  • The proposal link doesn't work, probably it refers to http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3406.html – alfC Jan 30 '15 at 16:30
2

(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

Community
  • 1
  • 1