Boost logo

Boost :

From: Fernando Cacciola (fernando_cacciola_at_[hidden])
Date: 2002-12-10 16:21:15


----- Original Message -----
From: "William E. Kempf" <wekempf_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Tuesday, December 10, 2002 12:43 PM
Subject: Re: [boost] Formal review: Optional library

> OK, I've been thinking about the interface a bit more and want to voice
> some other opinions. First, why were pointer semantics chosen at all?
> Why couldn't you have gone with value semantics?
>
> optional<int> foo()
> {
> //...
> }
>
> void bar(int value)
> {
> //...
> }
>
> optional<int> res = foo();
> if (res.initialized())
> bar(res);
>
> I can think of a few reasons, but want to hear about them from the
> designer, and want to see the rationale documented.
>
Fair enough.
Originally, optional<> had value semantics, so I used it pretty much like
in the examples you've shown.
But I had problem: since it had value semantics, it was perfectly OK to
write:

optional<int> x = foo();
if ( x == 0 )
  some();

Or even: if ( x ) some(x);

Meaning "compare the 'value' of x against 0".

Therefore, I needed something explicitely different to test
if it was initialized or not:

optional<int> x = foo();
if ( x.initialied() )
  some(x);

But if I wrote:

optional<int> x = foo();
if ( x )
  some(x);

it really looked as if it were testing if 'x' is initialized
while it was acutally comparing the 'x' value against 0.

Eventually, I realized that value semantics are not really appropiate to
deal
with unininitialized states since it blurs the distinction between
testing against other values and testing whether it is initialized or not.
So I wonder which other semantics would deal with it properly,
and pointers poped into my mind quickly.

> Now, assuming we stick with pointer semantics, let's make this as close to
> the "normal" smart pointer interface as possible:
>
>
> template <typename T>
> class optional
> {
> public:
> optional();
> explicit optional(T const& value);
> optional(optional const& other);
> template <typename U>
> optional(optional<U> const& other);
> ~optional();
>
> T& operator*() const;
> T* operator->() const;
> T* get() const;
> // T* release();
> void reset();
> void reset(T const& value);
> operator unspecified-bool-type() const;
> };
>
>
This interface is greatly simplified mainly because
my interface offers deep-constantness:

T const* peek() const ;
T* peek();
T const& operator*() const;
prxy operator*() ;

instead of:

T* peek() const ;
T& operator*() const;

The proxy is needed *only* to achieve deep-constantness.
If this feature weren't introduced, my interface would have looked
just like yours.

> Semantics and rationale:
>
> optional()
>
> Constructs an "uninitialized" optional.
>
> explicit optional(T const& value)
>
> Constructs an optional initialized to value through copy construction.
>
> optional(optional const& other)
>
> Copy constructs an optional using the value-type's copy constructor.
>
> template <typename U> optional(optional<U> const& other)
>
> Allows copy construction from "compatible" optionals (optionals with
> value types that can be used for constructing this optional's value
> type). I'm not sure how important this one is for optional, to be
> honest.
>
I've never needed to convert between optinals.
On a related note, if deep-constantess is not supported
(as in your interface), an implicit conversion from
optional<T> to optional<T const> is needed because
the user will have to use <T const> to attach constantess
to the value.

> ~optional()
>
> If the optional is initialized, calls the value-type's destructor.
>

Why isn't assignment supplied?

> T& operator*() const
>
> If the optional is not initialized, results in undefined behavior.
> Otherwise, returns a reference to the value.
>
> T* operator->() const
>
> If the optional is not initialized, results in undefined behavior.
>
> T* get() const
>
> If the optional is not initialized, returns 0. Otherwise, returns a
> pointer to the value.
>
> Rationale: This provides the interface to determine whether or not the
> optional has been initialized. Nothing else is required.
>
> // T* release()
>
> Rationale: Since ownership can not be transferred, this interface is
> not supported.
>
> void reset()
>
> If the optional is initialized, calls the destructor and sets the
> optional as "uninitialized".
>
> Rationale: Provides an interface to put the optional back into an
> "uninitialized" state.
>
> void reset(T const& other)
>
> If the optional is initialized, calls the destructor. Copy constructs
> the value in place.
>
> Rationale: Provides an interface to set the value for an optional in
> an "uninitialized" state. If there's some meta-magic that would allow
> you to use the value's operator=() if it has one this could be used as
> an optimization instead of the destruct/construct idiom.
>
I still don't like to think of optional<> as a container, so I still don't
like
this reset.
Currently, you can change the value of the optional directly without any
performance
penalty like this: *opt = new_val, so I don't see any real need for reset().

> operator unspecified-bool-type() const
>
> Returns an unspecified type (safe-bool idiom) that's convertable to
> bool, with a true value indicating the optional is not in an
> uninitialized state.
>
> Rationale: Allows for easier testing for the "uninitialized" state,
> especially for testing through assignments:
>
> if (opt = foo())
>
My motivation against this is just to prevent potential problems with
optional<bool>, but if we collectively conclude that those problems
are not so much important, I will add the safe_bool and
make sure to tell every not to use optional<bool> (and use tribool instead)
or else to use it with caution.

> This interface is nearly identical to std::auto_ptr, and as such should be
> instantly understandable by most C++ programmers. It provides all of the
> functionality present in the currently proposed interface, yet doesn't
> provide unnecessary duplicate interfaces or any use cases that should be
> ambiguous.
>
I notice that besides the deep-constantess difference, this interface is
also nearly identical to the current one except that it has no
value() nor initialized().

initialized() is there because there is no safe_bool() and some people
complained about having to use peek() or !; but I actually dislike it and
 I am about to remove it.

value() is there just because the proxy() fails to convert to T&;
and the proxy is needed by the deep-constantess,
so if this feature were to be dropped, value() will be (happily) drop too.

I'm not sure what would be effect of removing deep-constantness.
It would definitely allow the leaner interface you are proposing,
but it would allow something odd-looking like this:

void foo ( optional<int> const& opt )
{
  *opt = 3 ;
}

Of course, it can be argued that if you wanted
to convey true constantness you should write:

void foo ( optional<int const> const& opt )

instead.

This is a possibility.
I could accept something like this, though I constantly see
people complaining about lack of deep constantness on wrappers like
optional<>
What do others think?

Fernando Cacciola


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