Boost logo

Boost :

From: Vesa Karvonen (vesa.karvonen_at_[hidden])
Date: 2001-08-30 07:15:08


From: "Douglas Gregor" <gregod_at_[hidden]>
> On Wednesday 29 August 2001 08:47, you wrote:
> > From: "Douglas Gregor" <gregod_at_[hidden]>
[...]
> > It seems to me that 'swap', and probably all the other operations as well,
> > could easily be made exception safe if a 'move' operation would be
> > guaranteed to be exception safe:
> >
> > // pseudo code
> > swap(variant t_type t, variant u_type u)
> > {
> > uninitialized t_type tmp;
> >
> > // 3 exception safe moves:
> > tmp <- t; // effectively: "new (&tmp) t_type(t); t->~t_type();"
> > t <- u;
> > u <- tmp;
> > }
>
> This doesn't achieve the strong exception guarantee, however.

Sorry, I should have been more specific. I meant that the 'move' operation
would have nothrow guarantee. I think that such a move could be guaranteed for
many objects rather naturally, because it basically only changes the logical
address of the object. Such a 'move' operation would basically be unrelated to
copy assignment, copy constructor and the destructor. 'move' would be more
like a 'swap'.

However, I'm not sure how to do it in C++.

> If "u <- tmp"
> throws, t will already have changed, so we'll have a partial swap.

Yes, and rollback does not seem to be possible in general if a 'move' would be
allowed to throw.

> I haven't
> thouhgt about this long enough to be sure, but it seems like the strong
> exception guarantee is going to be hard to achieve

But is that really necessary? Like Dave Abrahams already hinted, the basic
guarantee seems to be satisfiable.

> // for this variant
> variant<int, float, std::string> v;
>
> // switch-on-type
> vswitch(v)(
> vcase<int>(function_object_taking_the_int_value)
> | vcase<float>(function_object_taking_the_float_value)
> | vcase<std::string>(function_object_taking_the_string_value)
> );

I like the above syntax. The biggest problems with it, making it difficult to
use, are due to limitations of the core language.

> > What do you think about the following table?
> >
> > default: ctor | type | state
> > -----------------------+----------+--------+-------
> > optional<t> | lazy | t | empty
> > any | lazy | void | empty
> >
> > Notes:
> > - 'optional' and 'any' can be 'empty'.
> > - At this point, to me, the (contained) type of 'optional<t>' always seems
> > to be 't'.
> > - 'any' has chosen to use 'void' as the type of the 'empty' state.
>
> Looks good. The contained type of optional<T> is essentially irrelevant when
> it is empty, so I think "undefined" is reasonable.

I also considered 'undefined', 'void' and 't'. 'void' does not seem
reasonable, because the interface of optional can not simultaneously return
both 't' and 'void' objects (nor 'empty' objects). Also, it isn't really the
type that is 'undefined'. It is the operations that return 't' that are
undefined when the state of an 'optional' is 'empty'. This seems very much
like the case with dereferencing a null pointer.

Now, assuming that 'optional' has semantics not unlike the C/C++ pointer, then
according to many modern programming languages and language designers, such
semantics should be avoided in favor of type safe semantics. Both 'optional'
and the C/C++ pointer are not type safe. 'variant', on the other hand, is type
safe.

> > Questions:
> > - Can a 'variant' be 'empty'?
>
> I personally would prefer that it never be empty. Why? If it can be empty,
> the above vswitch example doesn't handle all cases because the variant could
> be empty. Most likely, a default-constructed variant will default-construct
> the first type and take that as its value. No "clear()" or "empty()"
> operators will exist.

Ok. So, in order to have an 'empty' variant use 'variant<empty,...>'.

> > - What is the difference between 'variant' with and without 'void'?
>
> If a variant can be 'void', and its current value is 'void', then one can
> check if the currently held type is void but not extract the value. So in
the
> case of a vswitch() statement, vcase<void>(foo) will require that
> foo::operator() take zero arguments instead of one argument, requiring a
> bunch of extra code.

Ok. I think I now understand this issue. 'void' does indeed seem to complicate
things unnaturally.

> Did I mention that dealing with void is a pain? :)

Yes... ;-)

> So my solution is this:
>
> struct empty {};

I've seen that one before - many many times.

I would like to propose that Boost be simply added an 'empty' library:

    // boost/empty.hpp
    namespace Boost
    {
      struct empty {};
    }

...or it could have a different name like 'nil' or 'unused'.

> With the default-constructor-default-constructs-first rule one just declares
> a variant as: variant<empty, t0, t1, ..., tN> and it default-initializes to
> an "empty" object. No tricks, no workarounds. It looks nice in a vswitch
> statement, too:
>
> vswitch(v)(
> vcase<unused>(do_something)

I assume you mean 'unused' == 'empty'.

> | vcase<int>(do_something_else)
> ...
> );

> > I was mainly thinking of the following syntax:
> >
> > !x ...meaning... x.empty()
> > if (x) ...meaning... if (!x.empty())
>
> Perhaps for optional<T>, any, and a smart pointer, yes. I'm not sure that
> should be try for variant, because it will never be "empty" per se.

I think I'm now convinced that the pointer syntax should not be used for
'any'. I think that in order to avoid confusion, the syntax should only be
used for pointer like semantics.

> > Question:
> > - Is 'void' equivalent to 'empty'?
>
> I just made this question ambiguous by introducing the 'empty' type. Sorry
> :(.

No. I think that it was C that made it ambiguous a long time ago.

> For optional<T>, the type is fixed, so the question doesn't matter. For
> any, 'void' is used to signal 'empty'. For variant, the 'empty' notion
> shouldn't exist, IMHO, but could be added by the user.

...

> > Yes, I like it best, too. However, with my understanding of English, 'any'
> > does sort of imply that it is always something, which, in a sense, is not
> > true, because 'any' can be 'empty'.
>
> At the risk of sounding like a scratched compact disc, if void were a real
> type we wouldn't really need to have this notion of emptiness, we're just
> have a valueless sentinel type.

hmn... Perhaps any could also use 'empty' rather than 'void'?

It seems that 'variant' and 'any' are semantically much closer to each other
than to 'optional'.


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