Boost logo

Boost :

From: David Abrahams (dave_at_[hidden])
Date: 2002-08-09 13:29:58


-----------------------------------------------------------
           David Abrahams * Boost Consulting
dave_at_[hidden] * http://www.boost-consulting.com

----- Original Message -----
From: "Douglas Gregor" <gregod_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Friday, August 09, 2002 2:27 PM
Subject: Re: [boost] Re: Empty boost::variant semantics (was
Re:Re:Mini-reviewrequest: aligned_storage.hpp)

> On Friday 09 August 2002 12:52 pm, David Abrahams wrote:
> > > We decided long ago that #1 is bad, because it makes variants harder
to
> >
> > use.
> >
> > > But if we choose #2, then the current assign/swap semantics don't
meet
> >
> > the
> >
> > > basic guarantee!
> >
> > I don't understand why that must be the case. Are we still working with
> > assign_as<T>, swap_as<T> et al?
> > If so, it seems to me that these can just dispatch to the operation
defined
> > for T, and assuming T's assignment meets the basic guarantee, all is
well.
>
> Because we have to handle the case where we are assigning a T to a U. For
> instance:
>
> void foo(T& t, U& u)
> {
> variant<T, U> v(t);
> v = u;
> }
>
> Prior to the assignment "v = u", we have memory inside 'v' that stores a
value
> of type 'T'. After the assignment "v = u", that same memory inside 'v'
will
> store a value of type 'U'. Because the U needs to occupy the same space
as
> the T does now, the T must be destructed before the U is constructed.
> However, U's construction can throw so we need to save the value of T.

Why? Isn't "empty" one of the allowed states for the variant object?
If an exception is thrown during U's construction, you're left with an
empty variant. Seems simple enough to me. Besides that, you can't save the
value of T in any useful way without specific help from the T type (e.g. a
persistence mechanism -- and I don't think we want to go there).

> That's
> fine, but how do we _restore_ the saved value of T?

You can't, period the end. A destroyed object stays destroyed.

> For instance, say we do
> this:
>
> char the_memory[max_sizeof(T, U)];
>
> variant& operator=(const U& u)
> {
> T* asT = reinterpret_cast<T*>(&the_memory[0]);

I don't understand why the reinterpret_cast here. (T*)(void*) would be
safer.

> T savedT(*asT); // save a copy of the T value. it's okay if this throws
> asT->~T(); // destroy the T, won't throw
>
> try {
> new (&the_memory[0]) U(u); // might throw
> }
> catch(...) {
> // restore the T value
> }
> }
>
> What goes in that catch block? We can't copy-construct savedT into
the_memory,
> because that might throw and we'd be left with the_memory in an
inconsistent
> state. We can't use T's assignment operator because there's no T there.

Right. That's all in the domain of "heroic attempts to ressuscitate the
dead".

> > I don't expect to be able to repair exception-broken types (i.e. those
> > whose operations don't even meet the basic guarantee).
>
> That's fine. Unfortunately, the problem above occurs even with types
whose
> assignment and copy constructor meet the strong guarantee.

I still don't see the problem.

-Dave


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