Boost logo

Boost :

From: Douglas Gregor (gregod_at_[hidden])
Date: 2001-08-29 22:55:55


On Wednesday 29 August 2001 08:47, you wrote:
> From: "Douglas Gregor" <gregod_at_[hidden]>
> > Alignment & exception safety are somewhat at odds.
>
> ^ with each other?
> Could you elaborate?

See below.

> > For instance, if I have a
> > variants a and b and I want to swap them, it is tough (maybe impossible?)
> > to do this in an exception-safe manner for pathological types.
>
> 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. If "u <- tmp"
throws, t will already have changed, so we'll have a partial swap. 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

> > Safe vswitch is the easy part :).
>
> Yes, the only difficult part that I can see, at this point, is the syntax
> (of both interface and implementation).

It's not overly pretty, but my current suggestion is:

// 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)
);

The same syntax would be used for 'any', though there cannot be any checking.

> > > Now, if understand 'variant' correctly, then it seems to me that
>
> 'optional'
>
> > > could be implemented something like this (without run-time overhead on
> > > an ideal, but not "magical", compiler):
> > >
> > > template<class t>
> > > struct optional
> > > {
> > > ...
> > > private:
> > > variant<t,void> v;
> > > };
> >
> > Yes, though replace "void" with a real (but useless) type, like:
> > struct unused {};
>
> hmn... Do you mean that 'void' is an illegal parameter for 'variant'?

At the moment, yes. It could be hacked in to make it work, of course, it's
just that one can't have an instance of the void type (which is quite
annoying. I'm a big fan of making 'void' just an empty class and tossing out
all of its 'special' properties. It simplifies things a lot, and hurts
nothing).

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

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

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

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

> I find it natural to think about 'variant' and 'void' like this:
>
> default: ctor | type | state
> -----------------------+----------+--------+-------
> variant<t0,..,tn,void> | lazy | void | empty
> variant<t0,..,tn> | strict | t0 | !empty (always)
>
> [Having 't0' as the default type means just that the default type is chosen
> using some simple convention.]

I've got my heart set on not using void for this, partially because of the
problems it causes here and partially because I've had problems dealing with
void in the past. Boost.Function has to bend over backwards to deal with void
returns (indeed, it essentially maps void onto a real type, 'usable'), which
could completely be avoided if void were a real type. The special clause
allowing void returns disappears (it just calls an implicitly defined copy
constructor), and dealing with function objects of all types and shapes
becomes a whole heck of a lot simpler.

So my solution is this:

struct empty {};

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)
  | vcase<int>(do_something_else)
   ...
);

> > > - 'variant<T,void>' seems very close to 'optional<T>'.
> >
> > It is very close semantically.
> >
> > > - 'variant' and 'any' might or might not make use of some pointer
> > > syntax.
> >
> > Pointer syntax doesn't really make sense for variant or any, because
> > there
>
> is
>
> > no fixed return type for either. For optional and smart pointer, there is
> > only one possible type. This I think justifies the syntactic differences.
>
> 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.

> And possibly:
>
> 0 == x ...meaning... x.empty()
> x == 0 ...meaning... x.empty()

Please don't :)

> However, the usefulness of the above syntax is not very clear. It seems to
> me that 'vswitch' and 'cast' are the proper ways to use 'variant' and
> probably 'any', too.

I agree.

> Question:
> - Is 'void' equivalent to 'empty'?

I just made this question ambiguous by introducing the 'empty' type. Sorry
:(. 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.

> > > Initialization:
> > > - optional: lazy
> > > - variant: ???
> > > - any: lazy
> > > - smart pointer: strict (or this is how I see the matter)
> > >
> > > Should 'variant' support lazy initialization? Parhaps variant<> should
> > > support being left unitialized only if 'void' is one of the variant
> > > types.
> >
> > variant uses lazy initialization, though I can't say I like it much.
> >
> > > What about 'any'? Should it be 'any_or_none' or 'optional_any'?
> >
> > The name "any" seems reasonable...
>
> 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.

> > > Copy semantics:
> > > - optional: value
> > > - variant: value
> > > - any: value
> > > - smart pointer: reference, destructive copy or value (clone)
> >
> > Looks good. Not sure I agree with "destructive copy" or "value". I would
>
> just
>
> > say that smart pointers have reference semantics, but for objects with
> > managed lifetimes.
>
> Ideally (smart) pointers would simply have reference semantics, but there
> are many "smart pointers" out there which make the case more interesting.

True. Smart pointers seem to be most distinct from the other three classes we
are discussing, so I'm not worried about their semantics being different.

        Doug


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