Boost logo

Boost :

From: William E. Kempf (wekempf_at_[hidden])
Date: 2002-12-11 10:37:02


Fernando Cacciola said:
>> As did I in the interface I proposed elsewhere. My complaint wasn't
>> actually the lack of an operator=(), but of the lack of any way to
>> reassign the value in an efficient and uniform manner.
>>
> But what's wrong with: *opt = new_value?
> This is as efficient and as uniform as anything else.
>
> btw: your next response arrived so I can see that you think that *opt=x
> only works if opt is initialized...
> But it isn't the case!
> *opt=x actually initializes the optional if it not initialized. And this
> is why the proxy is needed...

Then your documentation is wrong! To quote:

"proxy optional<T>::operator* () ;

   If the optional object is initialized, returns a proxy object which
allows mutable access to its value (of type T).

   If it is uninitialized, the result is undefined (but BOOST_ASSERT is
used so the user can define this behavior in a debug build)."

I can not assign to an uninitialized optional through operator*() because
that results in undefined behavior. Now I assume that this was the reason
for the proxy... so you could assign to an uninitialized optional. But I
think that's a bad idea, because the assignment would be the only valid
thing you could do here, which is just plain confusing. But even if I
thought we should keep the concept of the proxy, your documentation is
simply incorrect.

> But then of course you will say:
> why don't you have *opt=x work *only* for initialized optionals, and use
> reset() to initialize it.

Yes!

> OK. This could work... I'll think about it further.
>
>> But that's not the case here. The current interface provides no way
>> to _efficiently_ change the value. (That is, with out regard for
>> whether or not the optional has been initialized.)
>>
> Actually, it does.
> *opt = new_value.
> changes the value even if it isn't initialized.
> But since you didn't expected such a behaviour it turns out that it
> might be confusing.

I didn't expect the behavior mostly because the documentation clearly says
this isn't legal. However, it would be surprising to include the single
use case of assignment to an uninitialized optional, yes.

> Clearly, if *opt=x only works for initialized optionals, reset() is
> needed.
>
> So we need to discuss this: should *opt=x assign even uninitialized
> optionals? (as it does now)
> Or should reset() do that?

My vote is for reset(), as it provides the cleanest interface.

>> > I know that since you construct an optional with a value, and since
>> you can turn an optional into the uninitialized state, it is
>> actually 'similar' to a container, but there is a difference: when
>> you construct an optional<> with a value, the optional uses the
>> value to initialize its own value, it is not 'takinging it' (it
>> isn't containing it). Similarly, when you 'uninitialize' an
>> optional, it is destroying its value, but it is not taking it out
>> (otherwise uninitialize would return the previosu value).
>>
>> I think you're trying to convey too much with the naming, with the
>> result being that the interface is actually _less_ understandable.
>>
> Notice that a smart pointer has a reset, but it takes a new pointer, not
> a new object value.

So what? Containers store values. So do smart pointers, even though the
value stored is a pointer.

> If optional<> had a resest, it would be *similar* to that of a smart
> pointer,
> but just similar because it would not take a pointer.

Which bothers me less than using an interface that's mostly likely a smart
pointer except in these unecessary details (the detail that it doesn't
take a pointer is necessary, but the others are clearly not).

> It would have to be assymetric with get(), for example.
> This is why I said that extending the analogy to a smart pointer too
> much might lead to confusion.

I think it leads to a more understandable interface... but we'll have to
let others voice their opinions as well.

>> I don't agree with that rationale. If it walks like a duck, and
>> quacks like a duck...
>>
> But does it walk and quack like a duck? or just looks like? :-))
> We need to figure this out, and we could use some external input for
> this...

It provides operator*(), and operator->(), so it walks like a duck and
talks like a duck.

>> > Now I want peek() even more than before, precisely to stress out
>> that is not containing
>> > a poiner.
>>
>> I don't think that peek() conveys that information. But let's assume
>> it does. Doesn't the name and purpose of optional<> already convey
>> that?
>>
> That?
> Do you understad what that the name peek() is intended to convey?

I believe so, and I believe that the name optional conveys it already.

> I will drop it if people want it that way, but I'd like it to be
> understood what peek() is about.
> It has nothing to do with optional<>, really, is about drawing a
> distinction between 'peeking' inside an object as opposed to
> 'extracting' from it (with a complete transfer or distribution of
> ownership).

But get() doesn't transfer ownership. release() does. And the name
optional conveys to me that the ownership can't be transfered.

>> Which I think is a mistake. In fact, it follows the full requirements
>> for smart pointers by one definition (as in it supports operator*()
>> and operator->(), which is all that's required to qualify as a smart
>> pointer).
>> To not follow the rest of the conventions for the (one) smart pointer
>> in
>> the standard seems to be a mistake that just leads to a less
>> understandable interface.
> But it can't follow the rest really.

What rest?

> It doesn't deal with pointers so it can't have a ctor taking a pointer,
> an assignment taking a pointer or a reset taking a pointer.
> These methods would not be alike those for smart pointers in spite of
> their appearance.

Smart pointers need not take a pointer for the constructor, etc. Many
valid smart pointers allocate their own memory internally, for instance.
This _one_ quality you see in smart pointers is something I don't think is
at all part of what makes a smart pointer.

>> > Are you thinking of a concrete scenario where this
>> > is the behaviour you want?
>> > I ask because I originally had optional<> follow precisely this
>> logic, mostly because I initially followed NANs behavior, but it
>> turned out that it didn't work in practice.
>>
>> If that's the case, then you need to provide the rationale for why it
>> doesn't work in practice.
> Fair enough.
>
>> It eludes me how it wouldn't work, since this
>> is truly precisely how pointers (which you're emulating) work.
> I don't follow. How does pointers work in this way?
> If you compare the 'values' of the objects being pointed to by two
> pointers and
> one of those pointers is NULL, the result is undefined just as in the
> case of
> optional<>:
>
> int n = 2 ;
> int* a = n;
> int* b = NULL ;
> if ( *a == *b ) // undefined.
>
> unless you think about comparing pointer values as oposed to pointee
> values:
>
> if ( a == b ) // defined,

That's what I was referring to.

> but this, anyway, is not defined as 'comparing to NULL yields false'.

It's not? 'b' is null, 'a' isn't, and 'a == b' yields false.

> (I see again the confusion drawn from pushing the analogy with pointers)

I think the analogy is accurate.

>> > That is, I never really wanted the comparison to return false; that
>> was just as wrong an answer as "true".
>> > Therefore, I always had to make sure the optionals were initialized
>> before even trying to compare them.
>> > There wasn't a single ocassion on which I could let an uninitialized
>> optional
>> > slip through a comparison with no problems trusting the 'false'
>> branch to take care of it properly.
>>
>> Why not? Why would an unitialized int ever be considered the same as
>> an initialized int (no matter its value)?
>
> Never.
> And just as never an uninitialized int would be considered *different*
> from an initilized
> int.

Why not?

> The comparison just cannot be made, so the result is neither true nor
> false.

I don't agree.

> Think about the interval library for instance.

That's a totally different domain, with different rules and requirements.

>> > I'm convinced now that comparing against an uninitialized optional
>> isn't either true nor false, is undefined.
>>
>> I'm not. Convince me.
>>
> I'll try.
> Think about a *concrete* example on which the 'false' branch
> in a piece of code is effectively capable of dealing with the fact that
> the values were never really compared because one of them was missing.
> Make the example such that you don't need to test for initialization
> first. That is, something of the form:
>
> if ( *a == *b )
> {
> }
> else
> {
> // doesn't matter that *a or *b is actually uninitialized.
> }

That works when "*a OR *b" is uninitialized. It may or may not work
depending on what you expect when "*a AND *b" are uninitialized... but I
think you can make that decision, document it, and have a useful
interface. Again, pointers work this way.

a = b = NULL;
if (a == b)
{
}
else
{
}

> In the cases I encounter this, it did matter.
> I'll see if I found a real example to show you.

That would help. It seems like the only cases I can think of would be
where you were abusing optional<bool> as a tribool.

>> > I've noticed in another post that Peter suggest that preventing this
>> sort of potential mistake does not worth the trouble; I'm not sure.
>> The danger is that all the code above which would compile would have
>> a completely
>> > different meaning that the one possibly intended (with *).
>> > So I think that such a conversion falls into the realm of implicit
>> dangerous conversions.
>>
>> Of which we have several today any way. Note that boost::shared_ptr<>
>> has this exact same potential problem, but the user base has not had
>> issue with it. (Also note that raw pointers would have the same
>> issue.)
>>
> It not is the same problem at all.
> You do not store a bool inside a shared_ptr<>,
> but you do store a bool inside an optional<>.

You don't?

boost::shared_ptr<bool> pb(new bool(true));

if (pb) // user meant if (*pb)

Granted, this is less likely, but it's still very valid.

And if we get a tristate bool in Boost, it's just as unlikely that anyone
would use optional<bool>, since the tristate bool would be more
appropriate for most use cases.

William E. Kempf


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk