Boost logo

Boost :

From: Gennadiy E. Rozental (rogeeff_at_[hidden])
Date: 2001-08-29 09:36:22


--- In boost_at_y..., "Fernando Cacciola" <fcacciola_at_g...> wrote:
>
> ----- Original Message -----
> From: Gennadiy E. Rozental <rogeeff_at_m...>
> To: <boost_at_y...>
> 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!

Not exactly and most important it implements deferent semantic -
value.
 
> 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.
I absolutely disagree. It SHUOLD NOT throw. If it will throw you will
need to catch it somewhere, while again you can just check ahead (see
above). And you have ho way to eliminate this - and catch exception
is much more bulky way. Also do not forget that every call (even if
you check already - compiler does not know about it) of value() will
include implementation specific exception handling logic. So instead
of transparent inline access method you propose function that will or
will not be inlined (implementation dependent) and will contain
exception handling logic.
 
> 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 )
I disagree again. First of all the fact that it will return false in
bath cases is GOOD, because t is NOT EQUAL to 2 - in most cases it is
exactly what you need. If you need to separate uninitialized case -
check it.

>
>
> 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.
>
What is confusung? First is check on whether or not t is initialized,
second is strange expression because t is VALUE. It is the same like
having int value to be compard with NULL. If t is optional for
pointer value (if anybody would ever need that) than !t and t == NULL
both will have the same semantic.

> 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.
Why do you need a exception than? The same statement could be writen
like
if( t.initialized() && t.value() == 2 )

operator bool and operator* are used just to shorten a code and does
not imply pointer semantic

> 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.

Again usage of operator* is just a short form of t.value(). I would
rather leave only t=3 possibility to clarify VALUE semantic. It could
be discussed.

> 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.

I think is value semantic is defined well enough. Usage of pointer
samentic worse from usage stand point (more typing), simplisity stand
point (it's more complex to implement) and perfomance stand point
(value semantic basicly is transparent, the only price is additianal
boolean value, while pointer semantic at least also require
additional dereferencing + value access method will always be
inefficient due to presence of exception).

>
> 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.

Summary - I do not see even 1 reason why pointer semantic could be
preferred over value semantic in ANY variation.

Regards,

Gennadiy.

>
> Regards,
>
> Fernando Cacciola
> Sierra s.r.l.
> fcacciola_at_g...
> 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