Boost logo

Boost :

From: Fernando Cacciola (fcacciola_at_[hidden])
Date: 2001-08-28 17:42:33


----- Original Message -----
From: Gennadiy E. Rozental <rogeeff_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Tuesday, August 28, 2001 3:59 PM
Subject: [boost] Re: Proposal: statefull objects

> Hi,
>
> I was interested in a proposed idea when you posted it. But I was not
> pleased with pointer sematic proposed for the class optional
> (statefull). The main point is that optional should cost as less as
> possible. So no additinal dereferensing should be present.
>
I've choosen pointer semantics because an instance of class optional<T>
should have a very well defined semantic w.r.t. the fact that it might not
be initialized (have no value). I tried lots of things before, and end up
convinced that only pointers provide a perfectly defined *not initialized*
state (the null pointer).

>Idea with
> runtime exception is also unclear.
If you use optional<> correctly you shouldn't get any exception. A
runtime_error is thrown only if you try to dereference a non-initialized
optional<> just as undefined behaviour ocurrs if you dereference a null
pointer.

> How do you imagine somebody would
> use it?
>
> optional<T> t = foo();
>
> try

> ...
> use *t
> }
> catch( runtime exception ) {
> do error processing
> }
>
> Instead it could be wirtten like this
>
> optional<T> t = foo();
>
> if( !t ) {
> do error processing
> }
> else {
> use *t
> }
>
And this is exactly how you can use it.
Notice that the boolean expression (!t) is meanigful with a pointer
semantic.

>
> But the original idea seems good, so I designed different class
> optional. I allowed myself to define operator* just to shorten code
> when used:
> (sorry alignment a little bit drift)
>
> #ifndef OPTIONAL_HPP
> #define OPTIONAL_HPP
>
> namespace boost {
>
> // Models a variable which has an associated 'm_initialized' state.
> // A default constructed instance of optional<T> is uninitialized.
> // An non-default constructed instance of optional<T> is initialized.
> //
>
> template<typename T>
> class optional;
>
> namespace detail {
>
> template<typename T>
> class value_proxy {
> public:
> explicit value_proxy( optional<T>& opt ) : m_opt(opt) {}
>
> operator T const&() const { return m_opt.value(); }
> optional<T>& operator=( T const& arg ) const;
>
> private:
> optional<T>& m_opt;
> };
>
> } // namespace detail
>
> template<typename T>
> class optional {
> public :
> typedef T value_type;
>
> // Constructors, allows implicit conversion
> optional()
> : m_initialized( false ) {}
> optional( const T& _v )
> : m_v( _v ), m_initialized( true ){}
> optional( const optional& rhs )
> : m_v( rhs.m_v ), m_initialized(rhs.m_initialized) {}
>
> // Assignment operators
> optional& operator=( const optional& rhs ) {
> try {
> if( rhs.m_initialized )
> m_v = rhs.m_v;
> m_initialized = rhs.m_initialized;
> }
> catch( ... ) {
> m_initialized = false;
> throw;
> }
>
> return *this;
> }
> optional& operator=( const T& rhs

> return operator=( optional( rhs ) );
> }
>
> // Value access
> T const& value() const { return m_v; }
> T& value () { return m_v; }
> T const& operator*() const { return value(); }
> detail::value_proxy<T> operator*()

> return detail::value_proxy<T>( *this );
> }
>
> // Is initialized checks
> bool initialized() const { return m_initialized; }
> bool operator!() const { return !initialized(); }
> operator bool() const { return initialized(); }
>
> // Comparison operators
> friend bool operator==( const optional& lhs, const optional&
> rhs ) { return lhs.compare( rhs.get() ); }
> friend bool operator!=( const optional& lhs, const optional&
> rhs ) { return !lhs.compare( rhs.get() ); }
> friend bool operator==( const optional& lhs, const T&
> rhs ) { return lhs.compare( rhs ); }
> friend bool operator!=( const optional& lhs, const T&
> rhs ) { return !lhs.compare( rhs ); }
> friend bool operator==( const T& lhs, const optional&
> rhs ) { return rhs.compare( lhs ); }
> friend bool operator!=( const T& lhs, const optional&
> rhs ) { return !rhs.compare( lhs ); }
>
> private:
> // Comparison helper
> bool compare( const T& rhs ) const

> return m_initialized ? m_v == rhs : false;
> }
>
> // Data members
> T m_v;
> bool m_initialized;
> };
>
>
> template<typename T>
> inline optional<T>&
> detail::value_proxy<T>::operator=( T const& arg ) const

> m_opt = arg;
>
> return m_opt;
> }
>
> } // namespace boost
>
> #endif // OPTIONAL_HPP
>
> /////////////////////////////////////////////////////////////////
> /////////////////////////////////////////////////////////////////
>
> This is an alternative. What do you think?
>

Interestingly, your version looks exactly like my first version!

Here are some problems:

1) optional<int> t = foo();
      if ( t.value() == 2 ) ....

If t is unitialized, then t.value() MUST throw, since it isn't true nor
false that its value is 2 because it has no value.

2) if ( t == 2 ) ...

Again, if t is unitialized the above boolean expression returns false, but
that's a mistake since, for instance, it also returns false for (t != 2 )

3) if (!t)
    if ( t == NULL )

This is confusing, the first expression evaluates whether t is initialized
or not, but the second test its value against the integer 0.

Problems 2 and 3 reveals that it is not a good idea to allow direct
comparisons between optionals and values.
Now, if the only supported syntax is: if ( *t == 2 ) the semantic is
preserved, becuase if t is uninitialized it throws before the operator == is
executed.
To prevent yourself of having to use a try block you would write: if ( t &&
*t == 2 ) ,
which is safe and completely readable once you know that t has pointer
semantics.

4) *t = 3
      t = 3

Since you provided implicit conversion and assignment from T, both
statements above have the same meaning.

You might not provide non-const operator *(), so only the second form is
allowed, but that would be confusing since there is a const operator*().
Therefore, since *t=3 must be supported, supporting also t=3 is confusing.
For this reason I intentionally disallowed implicit conversions.

In other words, it is much more important that optional<> have well defined
semantics than that it be *easy* to use (easy=less typing). I've played A
LOT with all kinds of variations during the last months and I am convinced
that the only truly well defined semantic for values that might be
uninitialized is that of a pointer.

Anyway, I realized a few things with my implementation seeing yours:

) T const& *() const ; seems to work just fine. I can't remember why I
changed it to use the proxy.
) I lack exceptionn saftey in operator =.
) I forgot the 'explicit' in the proxy.

Regards,

Fernando Cacciola
Sierra s.r.l.
fcacciola_at_[hidden]
www.gosierra.com


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