From: Douglas Gregor (gregod_at_[hidden])
Date: 2002-08-11 09:57:13


On Sunday 11 August 2002 03:31 am, Eric Friedman wrote:
> Introducing move semantics is all-good and well, and I think it's
> something Boost should "standardize" anyway, but I think we also need
> to figure out a good way to keep the non-empty invariant for
> boost::variant -- even when its bounded types do not provide the
> nothrow-move guarantee.

Agreed.

> I think the most important point, however, to draw from the proposal is
> that the two classes would really be one and the same if the set of
> bounded types provides nothrow-move semantics.

There is _so_ much overlap between these two classes that I wonder why we
should even separate them? I'm thinking of this from the perspective of
someone who would have to write a C++ style guide: how would you lay down the
rules w.r.t. using safe_variant vs. variant? Use safe_variant always, because
we might not ever be able to afford the strange behavior of empty variants?
Use variant always, because we don't care about exceptions anyway? Or, use
safe_variant when required and variant otherwise?

I think we should stay with one variant class. It should, by default, always
be safe. However, because there are types that can't safely be put into a
variant without compromising speed and/or efficiency, we should:

  1) Make a reasonable decision between the two safe storage methods (heap
allocation vs. doubling the stack allocation). Perhaps use an heuristic such
as: if in a variant<T1, T2, ..., TN>, type Ti is unsafe, then:
    a) if sizeof(Ti) >= sizeof(variant<T1, T2, ..., T{i-1}, T{i+1}, ..., TN>)
then allocate Ti on the heap
    b) otherwise, allocate Ti by doubling the stack allocation.

  2) Optionally warn the user at compile-time when making this type of
decision. This lets the performance-savvy users know if variant is being
pessimistic so they can tweak things (see #3).

  3) Allow wrappers to designate the type of storage used. We already have
incomplete<Ti> for heap allocation. Perhaps a the complete list should be:
    incomplete<Ti> or on_heap<Ti>: allocate on the heap
    on_stack<Ti>: on the stack, potentially doubling the size
    unsafe<Ti>: on the stack, without doubling, but variants may become
singular/empty if an assignment or swap involving this type throws.
    trivially_movable<Ti>: on the stack, and we can move with memcpy (this is
shorthand notation for specializing some move-semantics traits)

This way we've guaranteed safety barring the presence of 'unsafe<T>' but also
optionally warned the user about potential efficiency issues and given
him/her a way to manually resolve the efficiency issues on a type-by-type
basis based on the requirements of his/her project (space may be more
important in one area, whereas speed is more important in another).

        Doug