Boost logo

Boost :

From: Fernando Cacciola (fcacciola_at_[hidden])
Date: 2001-08-29 11:19:30


----- Original Message -----
From: Gennadiy E. Rozental <rogeeff_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Wednesday, August 29, 2001 11:36 AM
Subject: [boost] Re: Proposal: statefull objects

> --- 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
> >
> >
> > [SNIP]
> > Interestingly, your version looks exactly like my first version!
>
> Not exactly and most important it implements deferent semantic -
> value.
>
I was referring to the first version I personally used (for months) long
before I ever post this into boost.
That version was nearly identical to yours: It allowed implicit conversions
from T, it provided operators ==,!= which returned false if either operand
is unitialized, and provided operator* just as a shortcut.

> > 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.
>
Actually, my implementation dosen't include any exception logic in the
accesor function, so it will be inlined by any compiler with any inlining
support.

T const& value() const
{
  if ( !v )
    error() ;
  return *v ;
}

As it is documented in the code, the member function error() is
intentionally not inline, this effectively allows value() to be fully
inlined, and since it is only called if the expression !v is false, the only
overhead is in the check.

Furthermore,

It throws ONLY IF THERE ISN'T ANY VALUE to return.
In your design, it returns a default-constructed value of type T, but it is
technically a lie that the value of t is T() since it is actually has no
value.
As a user you CANNOT call value() in an uninitialized optional. This is
under any criteria an ERROR, and this is true regardless whether value()
throws or returns a fake value.
In other words, you should aways -one way or another- verify if the optional
is initialized before trying to access its value.
Therefore, at the point where you call value() (or any shortcut, such as
operator*), the optional MUST be initialized, otherwise there is an error in
the program logic.

Do you disagree with the statement that calling value() on a uninitialized
optional is under any circunstance an error?

Assuming that you agree with that, what would happen if the user of your
design simply forgets to verify that t is initialized and calls t.value()?
Do you think that there is any way for him to recognize that the T()
returned from value() is actually meaning that 't' isn't initialized?

Now, if your argument is based on the assumption that calling value() on an
initialized optional is an error, but that it is not the responsability of
the optional class to signal that, then I would like to know how do you
think a program can be robustely constructed if the programmer must verify
every expected condition by itslef since the system won't tell him nothing
about any violations. Exceptions were added precisly to prevent a program
from continue running after such violations have been made.

You DONT NEED TO USE ANY TRY BLOCKS in order to use my optional. An
exception will be thrown ONLY IF YOU MAKE AN ERROR IN YOUR CODE. Don't
throwing such exception will just MASK the error, while throwing will help
you recognize that you've made it.

Once again: If you don't forget to verify that the optional is initialized
you won't have any exception, but if you do forget it, you don't really
wont to let your program keep running because it might be based on the false
belief that a particular optional has a value that it actually hasn't.

> > 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.
>
The logical fact that an object which has no value is neither equal nor
not-equal to anything dosen't imply that it is a good idea to design logical
operators that way.

optional<bool> s = foo() ;

if ( s != false )
{
   s MUST BE TRUE here, because that's what the semantic of the if clause is
implying.
}

The above code is broken since I forgot to verify that t is initialized.

Now, if I must write it like:

optional<bool> s = foo() ;

if ( *s != false )
{
   s MUST BE TRUE here, because that's what the semantic of the if clause is
implying.
  And it is guaranteed to be true because if t isn't initialized this block
isn't entered since operator*() will throw.
}

The mistake I made (not verifying t) will be caught by the exception so the
program preserves its logic.

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

And if I write it like:

optional<int> t ;
if (!t)
if ( t == 0 )

Don't you think that most people -including yourself- will believe that both
expressions have the same meaning?

>
> > 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.
>
If optional<> would have value semantics as your are proposing, then the
implicit conversion will be OK.
You will agree, I assume, that if it must have pointer semantic then the
implicit conversion is unapropriate.

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

Value semantic is unapropriate. The reason is that since there is no value
that can signal uninitialized state, as soon as the user forgets to verify
if the optional is initialized -which is a logic error-, the program just
keeps running unaware of the error leading to unexpected behaviour which can
be a LOT worse than abnormal termination.
If you are not yet conviced, I invite you to actually use your class, with
your semantic, in working code, for a few months. That's what I needed to
convince myself that only pointers support a well defined 'uninitialized'
state.

> Usage of pointer samentic worse from usage stand point (more typing),
True, but we are not talking about a huge amount of additional typing.
Rememer that if you use optional carefully you don't need to worry about any
exceptions.
(as much as you don't need to worry about dereferencing null pointers)

> simplisity stand
> point (it's more complex to implement)
It is not that much complex! And it is already implemented :-)

> 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).
>
The only performance differenece is in the extra check.
The exception is thrown from a non-inline function.

> >
> > 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.
>
In that case feel free to submit your own class. Eventually, the boost
formal review process will pick one to be included.

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