$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
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