|
Boost : |
From: Fernando Cacciola (fcacciola_at_[hidden])
Date: 2002-06-12 14:18:40
----- Original Message -----
From: "Douglas Gregor" <gregod_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Wednesday, June 12, 2002 3:25 PM
Subject: Re: [boost] Proposal --- A type-safe union
> [snipped]
>
> > 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.
>
> There's a lot of complexity in supporting direct, full-featured recursion
> (e.g., std::vector<rec>). However, supporting just incomplete types should
be
> quite easy.
>
OK.
>
> If your only objection to supporting incomplete types is the
implementation
> complexity, then I still think that we should support incomplete types.
The
> handle-body scheme is indeed more flexible, but it is a maintenance
nightmare
> without an automated scheme for creating the handle classes.
>
OK. I'was actually fearing a complexity that resulted in compilers not being
able to use the library.
If the implementation can be made to work with the most popular compilers, I
have no problem.
>
> Direct recursive types (e.g., using 'rec'), however, might be too complex
to
> justify. I pursued them under the somewhat misguided notion that they were
> needed to easily support a pattern-matching typeswitch statement. However,
it
> appears that recursive types aren't at all necessary for that, and that
I've
> already written a typeswitch that does essentially what I wanted:
>
> http://groups.yahoo.com/group/boost/files/typeswitch-20020413.tgz
>
> The implementation uses some traits on sum types (boost::any was used for
the
> experiment) and product types (boost::tuples::tuple was used for the
> experiment) to implement a generic typeswitch statement. The example from
the
> tarball is the following:
>
Cool... I'll take a look at it.
> --------------------------------------------------------------------------
-
> using namespace boost::tuples;
>
> typedef any Expr;
> typedef tuple<int> Literal;
> class Add : public boost tuple<Expr, Expr> {};
> class Sub : public boost tuple<Expr, Expr> {};
> class Mul : public boost tuple<Expr, Expr> {};
> class Div : public boost tuple<Expr, Expr> {};
> // some simple trait specializations
>
> int compute(Expr expr)
> {
> using namespace boost::lambda;
>
> return switch_<int>(expr)[
> case_<Literal>((_1, _1))
> | case_<Add>(bind(&compute, _1) + bind(&compute, _2))
> | case_<Sub>(bind(&compute, _1) - bind(&compute, _2))
> | case_<Mul>(bind(&compute, _1) * bind(&compute, _2))
> | case_<Div>(bind(&compute, _1) / bind(&compute, _2))
> ];
> }
> --------------------------------------------------------------------------
-
>
Yes, it looks very good .
>
> We could take a similar approach by allowing only incomplete types, and
not
> use the 'rec' trickery at all. The same code above could be used with a
> hypothetical variant type like this:
>
> --------------------------------------------------------------------------
-
> typedef tuple<int> Literal;
> class Add;
> class Sub;
> class Mul;
> class Div;
>
> typedef boost::variant<incomplete<Add>, incomplete<Sub>,
> incomplete<Mul>, incomplete<Div>,
> Literal> Expr;
>
> // same definitions for Add, Sub, Mul, and Div
>
> // compute function doesn't change, but now we get static
> // completeness-of-decoding checks
> --------------------------------------------------------------------------
-
>
> I still don't know how to do more complex pattern matching that can check
> substructures, e.g., match a tree like:
>
> Add
> / \
> X Sub
> / \
> Y X
>
> where X and Y are expressions, and the two X's are equal by the '=='
operator.
> But this can be solved separately from the implementation of the variant,
we
> just need a good way to represent the trees. But I digress...
>
> > 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.
>
> I'd prefer
> variant<int, float, double, std::string>
> to the typelist equivalent
> variant<TYPELIST_4(int, float, double, std::string)>
> or
> variant<boost::mpl::list<int, float, double, std::string> >
>
> because I can't see any interface-driven motivation to use typelists, so
long
> as the maximum number of types can be changed if necessary. Also, both
Tuples
> and Function use separate arguments instead of dot/type-lists.
>
> I think the error messages might be more readable because the typelist
> implementation I have in mind introduces a LOT of extra template levels to
> muddy up the terminal window. (It's shown below, but not by me)
>
> Oh, and the implementation is easier :)
>
OK.
Let's say then that the extensive-list interface is better simply because it
is easier to use (and easier to implement :-)
>
> > > 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.
>
> Brad King apparently took my laziness as a challenge and sent a solution
:)
>
> --------------------------------------------------------------------------
-- > #include <iostream> > #include <typeinfo> > > struct nil {}; > > template <typename Car, typename Cdr=nil> > struct list > { > typedef Car car; > typedef Cdr cdr; > }; > > template <typename List> > struct checker: public checker<typename List::cdr> > { > typedef checker<typename List::cdr> derived; > using derived::check; > static void check(typename List::car) > { > std::cout << "check(" << typeid(typename List::car).name() << ")\n"; > } > }; > > template <> > struct checker<nil> > { > static void check() {} > }; > > int main() > { > typedef list<int, list<char> > my_list; > typedef checker<my_list> my_checker; > my_checker::check(1); > my_checker::check('c'); > return 0; > } > -------------------------------------------------------------------------- --- > > The important part is the 'using derived::check', which ties together all of > the overloads. I don't know how (practically) portable this is: GCC 3.x > handles it fine, but GCC 2.95.x does not. > Interesting: I tried it and it didn't work with BCB5. It ICEs right in the using declaration. But, fortunately, it is not required! As long as the inheritance is public there is no need to bring each 'check' up-front. It works fine if the 'using' declaration is removed. Fernando Cacciola Sierra s.r.l. fcacciola_at_[hidden] www.gosierra.com
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk