Boost logo

Boost :

From: Fernando Cacciola (fcacciola_at_[hidden])
Date: 2002-06-12 12:18:10

----- Original Message -----
From: "Douglas Gregor" <gregod_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Wednesday, June 12, 2002 12:15 PM
Subject: Re: [boost] Proposal --- A type-safe union

> On Wednesday 12 June 2002 10:01 am, Fernando Cacciola wrote:
> [lots of snipping going on]
> > > > 2) Automatic casting: (i) never, (ii) cast to the
> > > > first type possible, (iii) cast if only one possible
> > > > type found
> >
> > What means 'automatic' casting? If it means implicit conversions, I
> > this could be achievable by having the set of operator Tn() in the
> I thought by 'automatic' casting Itay meant 'implicit conversion to a
> type', e.g.,
> void foo(const variant<int, float>& v);
> // ...
> double pi = 3.14159;
> foo(pi); // okay, stored as a float in v
Aha, I see. I though he referred to exactly the opposite. :-)

> > > How about:
> > > (iv) use overload resolution to choose the best type, or fail if no
> > > best type exists
> >
> > Maybe I didn't understand point (2), but how would we use overload
> > resolution and what for?
> Overload resolution would be used to choose the best storage type within
> variant given an object of a type that isn't in the variant. The example
> above would choose to store the double as a float because that is a better
> match than storing the double as an int.
Yes, I see it now in this context.

> > > Again, I don't see a need for a policy here. Overload resolution is a
> > > feature that every C++ programmer is aware of and it seems the natural
> > > solution for this component. Here are the reasons why I reject the
> > > three:
> > >
> > > (i) implicit conversion is a feature of C++. Suppressing implicit
> > > conversions should be performed by user types, not by the variant,
> > > because the set of implicit conversions for a type is a property of
> > > type
> >
> > itself,
> >
> > > not a container of that type.
> >
> > I agree that a variant should be implicitely convertible to any of its
> > types.
> We're talking about different things here :)
> FWIW, I believe that we should allow implicit conversion to a variant from
> type, but that we should NOT allow implicit conversion from a variant to
> of its types.
> Implicit conversion from a variant to any of its types strikes me as being
> very, very dangerous. Extracting a value of a specific type from a variant
> a dangerous operation, because one needs to check first if that type is
> actually the stored type. Now, invoke that dangerous operation using
> conversion (the user may not even realize a conversion is happening!) and
> think we've whipped up a recipe for run-time disaster.
I normally consider implicit conversion very dangerous and I usually avoid
It appears that I've considered this variant type to be modeling a different

I thought of variant<T,U,V> as a type whose value is *equally* any of T, U
or V.
But this variant seems to be a type whose value is *only one of* T,U, V (but
not all at the same time).
(Now that I think of it, 'discriminated' would mean just that, wouldn't it?)

Assuming I was just wrong about the whole idea of a variant, all you've said
about implicit conversions is totally right.

> > I don't know about incomplete types.
> > In my view, T* is not an incomplete type; it is a perfectly complete
> > of pointer type.
> > If I put a pointer to T in a discriminated union, I want the 'pointer',
> > the 'T' instance, to be represented by the union.
> > That is, the union deals with the pointer value only; to whatever it is
> > pointing to, and how that is allocated, is IMO, totally unrelated.
> > I don't think there is a really good reason to have a truly-incomplete
> > in an union.
> My example could have been better :) Pointer types are indeed types.
> it seems that it would be useful to be able to store an incomplete type
> without resorting to pointers. Basically, you're asking the variant to
> the type like it is a pointer, but hide the fact that it's a pointer from
> user of the variant.
> It could help with recursive types where the 'rec'
> approach is clumsy, for instance:
> typedef variant<incomplete<IfThenElse>,
> incomplete<WhileLoop>,
> incomplete<ExpressionStatement> > Statement;
> Here, IfThenElse, WhileLoop, and ExpressionStatement will all be classes
> will contain Statements, so they can't be defined until Statement is
> Essentially, they are all recursive types that aren't defined using 'rec'.
> would be a pity to have to deal with pointers in a case like this.
> > As for recursive types, the tag approach (using 'rec') would allow
> > to be fully stack-based. No need for heap allocation within variant.
> There's more trickery in the 'rec' approach that I didn't describe.
> the above example, we might want to represent a block of statements using
> std::vector, e.g.,
> typedef variant<incomplete<IfThenElse>,
> incomplete<WhileLoop>,
> incomplete<ExpressionStatement>,
> std::vector<rec> > Statement;
> Now, std::vector<rec> isn't really useful because it can't possibly act
> std::vector<Statement>, which is what we really wanted. The trick is to
> 'rec' just a placeholder that will be replaced by the actual type when the
> variant is used. So Statement actually stores a block of statements as
> std::vector<Statement>, and the user sees it as std::vector<Statement>.
> This type of thing may or may not require heap allocation. For an
> stack allocation will still work because std::vector<Statement> doesn't
> depend on the size of Statement, but for other types, e.g.,
> std::pair<Statement,Statement>, the size does depend on the size of
> Statement, so we must use heap allocation. We can't tell if an arbitrary

> class template bases its size on its template parameters, so we have to be
> pessimistic and assume that heap allocation is required for recursive
> Of course, we could supply a 'complete' wrapper so the user can state that
> the object size is independent of the variant type size, e.g.,
> typedef variant<incomplete<IfThenElse>,
> incomplete<WhileLoop>,
> incomplete<ExpressionStatement>,
> complete<std::vector<rec> > > Statement;
> IfThenElse, WhileLoop, and ExpressionStatement objects will be
> whereas std::vector<Statement> will be stack-allocated.
OK, I see it.
I notice that there is a lot complexity just to support incomplete types and
full-featured recursion.
Incomplete types "could" be detached from the variant itself by an
appropiate handle-body scheme.
It does involve more work for the user, but it also unbound all the possible
decisions (allocation,ownership, etc...).

I think that a variant type supporting incomplete types *might* be useful.
Though, I still need to see a complete example were a handle-body pattern is
not better from an engineering perspective.
But OTOH, I think it will introduce significant complexity. It might be a
good idea to at least have a light-weight variant too.

> > > template<typename T1, typename T2 = unused2, ..., typename TN =
> > > class variant
> > > {
> > > public:
> > > variant(); // default-construct value of type T1
> > >
> > > variant(const T1& t1); // assign value t1
> > > variant(const T2& t2); // assign value t2
> > > // ...
> > > variant(const TN& tN); // assign value tN
> >
> > I'd like you to elaborate on the need of an extensive-list instead of a
> > dot-list.
> It's not an absolutely need, but it does make things easier. See how each
> the possible types is listed in a separate constructor? When a variant is
> constructed from any type, function overloading is used to select the most
> appropriate storage type in the variant for the incoming object. With a
> typelist/dot-list approach, you can't just list all of the constructors so
> you are forced to deal with overloading yourself.
It seems to me that the choice of extensive-list vs dot-list is driven by an
implementation concern.
So, from the user perspective, is there any significant difference? Would it
be better for the user to use a dot-list?
I think not, but still this desicion should not be so much affected by the
choice of matching mechanism; it should be based primary on the effect on
the interface.

> If you are really interested, there is a way to make the compiler perform
> overload resolution for an arbitrary-length typelist, but I'm not going to
> type out an explanation unless someone asks specifically :)
Well... You bet I'm curious... but I wouldn't ask you to explain it just to
satisfy my curiousity :-)
If you ever do explain it, just let me know.

> > >
> > > ~variant();
> > >
> > > void swap(variant& other); // swap values
> > >
> > > variant& operator=(const T1& t1); // assign value t1
> > > variant& operator=(const T1& t1); // assign value t1
> > > // ...
> > > variant& operator=(const TN& tN); // assign value tN
> > >
> > > variant& operator=(const variant& other); // assign value from other
> > >
> > > // semantics described above
> > > template<typename OT1, typename OT2, ..., typename OTN>
> > > variant& operator=(const variant<OT1, OT2, ..., OTN>& other);
> > >
> > > const std::type_info& type() const; // typeid(type of current value)
> >
> > What is the 'current' value of a variant?
> It's accessible via 'variant_cast', which I had previously defined as:
So.. If I got it right, this variant has -at a given time- a value of 'one'
of Tn, correct?

> template<typename T, typename T1, typename T2, ..., typename TN>
> T& variant_cast(variant<T1, T2, ..., TN>& v);
> template<typename T, typename T1, typename T2, ..., typename TN>
> const T& variant_cast(const variant<T1, T2, ..., TN>& v);
> > I'll add:
> >
> > operator T1 const& () const ;
> > operator T1& () ;
> I stated my objection to this earlier, but I should probably say a little
> about what features I hope to see for accessing the value in a variant. As
> see it, there would be three mechanisms for extracting values from a
> 1) variant_cast: this one is the least safe, because there is only the
> guarantee that it will throw an exception if it is used to try to cast to
> value type that isn't currently stored. It's mainly here for boost::any
> compatibility.
> 2) a visitor mechanism: this one is completely safe, because at
> it is ensured that there is a visitor function overload that can accept
> of the stored value types of the variant. We discussed the visitor
> 3) a typeswitch mechanism: this one is also completely safe, because we
> check at compile-time if every possible stored value type is handled by
> switch statement. It could also be extended to support pattern matching.
> So with two typesafe options for extracting values from a variant, I don't
> a need for the unsafe implicit conversions.
Yes, I understand (I think:-)
The key point is that the value is of only one of the possible types, not
all of them simultaneously.


Fernando Cacciola
Sierra s.r.l.

Boost list run by bdawes at, gregod at, cpdaniel at, john at