Boost logo

Boost :

From: David Abrahams (dave_at_[hidden])
Date: 2003-10-10 21:33:14


Eric Friedman <ebf_at_[hidden]> writes:

> I'd like to direct the recent discussion on variant toward a conclusion.
>
> I think it is very important to decide how a variant should behave in
> general after a failed assignment. There are two options: 1) the
> variant contains an initialized value of one of its bounded types; or
> 2) the variant is singular. (For reference, I provide a summary of the
> semantics and efficiency of these two options at the end of my
> message.)
>
> The question that needs to be answered is the following:
>
> "Is a singular variant worse than one with unspecified content?"
>
> To explain, I suggest the following example:
>
> T2 g();
>
> void f(variant< T1,T2 > & v)
> {
> v = g();
> }
>
> Assume that upon entering f, v contains value of type T1. Assume
> further that the assignment v = g() fails with an exception.
>
> For the caller of f, and for any other part of the program that may
> contain a pointer or reference to v: is error-recovery possible if v
> contains an unknown value of either type T1 or T2 (i.e., variant
> implements option 1)?
>
> Since the value of v is in general wholly unspecified, it seems to
> me that the only way to ensure meaningful content for v is another
> assignment. This suggests to me that a singular variant is no worse
> than one with unspecified content.

That may be true, but I don't think so. The idea of a special
error-only singular state makes me nervous. It means that you always
need to be aware of every bit of code where you might be dealing with
the singular state, and those conditions can change in ways that break
encapsulation. For example, if you write a function which ignores the
possibility of the singular state, because you know you're only using
it on "good" objects. Then one day you find you want to use the
function in some destructor... but that might get invoked during stack
unwinding from an exception that throws the very object in question
into its singular state. insidious, isn't it?

That's why I favor an interface which, in order to allow the empty
state, makes it an normal part of the type's range of values: the
interface where you add empty_type explicitly is one way to do that.
Another way to do that is to automatically and always inject
empty_type into the list of types.

> Am I overlooking something?

I think you missed this:

In the case where T1 and T2 both provide strong guarantee assignment,
option 1 always means that the variant always contains a value that
the user initialized the variant with or assigned into it - it is
never indeterminate.

> Thanks,
> Eric
>
> ---
> Option 1. With this option, the variant continues to contain an
> initialized value of one of its bounded types, but it does not
> guarantee any particular content value or type. In terms of
> implementation, this requires that some sort of fallback strategy be
> available for variant in the event of an exception. If one of
> variant's bounded types is nothrow default-constructible, then
> construction of such a type constitutes an appropriate fallback
> strategy. In the general case, however, the fallback strategy must
> rely on either double storage within the variant or temporary backup
> on the heap. [Currently, variant implements this option, using heap
> backup as its general fallback strategy.]
>
> Option 2. With this option, the variant does not contain any value,
> and the result of most operations on the variant are undefined. The
> only defined operation is assignment of a nonsingular variant to the
> singular variant.

Seriously? You can't even ask the variant if it contains a particular
type? That seems draconian.

> (These semantics mirror the STL's concept of singular iterators; see
> http://www.sgi.com/tech/stl/trivial.html.)

Yes, but the standard iterators don't enter into singular states just
by virtue of an exception being thrown (OK, the standard doesn't say
this, but it probably should - and none of the standard *container*
iterators throw at all). Furthermore, achieving singularity is part
of the iterator's normal interface (default-constructed), not a
special error-only state.

> In terms of implementation, this requires a simple check in
> variant's destructor to handle the special case of a singular
> variant. Neither heap backup nor double storage is necessary.

Anyway, I hope you understand my position on this. Please ask for
clarification if not. Aside from that, I'm not going to argue with
anything you decide to do; I trust you to make a sensible choice.

-- 
Dave Abrahams
Boost Consulting
www.boost-consulting.com

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