|
Boost : |
Subject: Re: [boost] [guidelines] why template errors suck
From: Smith, Jacob N (jacob.n.smith_at_[hidden])
Date: 2010-09-29 18:00:35
> -----Original Message-----
> From: boost-bounces_at_[hidden] [mailto:boost-
> bounces_at_[hidden]] On Behalf Of Eric Niebler
> Sent: Wednesday, September 29, 2010 9:35 AM
> To: boost_at_[hidden]
> Subject: Re: [boost] [guidelines] why template errors suck
>
> (Sorry for going AWOL. I was on a plane most of yesterday, and will be
> away from the computer most of today. But this thread has been very
> illuminating.)
>
> On 9/28/2010 2:14 AM, David Abrahams wrote:
> > This would be a way to solve the same problem
> > without violating the spirit of concepts:
> >
> > namespace my_limited_domain
> > {
> > concept IntValued<typename T>
> > {
> > int value(T);
> > }
> >
> > concept_map IntValued<int>
> > {
> > int value(int x) { return x; }
> > };
> >
> > template <IntValued L, IntegralValue R>
> > concept_map IntValued< std::pair<L,R> >
> > {
> > int value(std::pair<L,R> const& x)
> > {
> > return value(x.first) + value(x.second);
> > }
> > };
> >
> > template <IntValued X>
> > int sum_ints(X const& a) // rename to "get_value"
> > {
> > return value(a);
> > }
> > }
>
> Got it. You solved this particular problem by moving the logic, the
> very
> algorithm itself, into a concept map. (And Steven anticipated where I
> was going with this by bringing up Fusion algorithms, because this is
> essentially a trivial (segmented) Fusion algorithm.) Putting the
> algorithm into the concept map is clever, but I find it somewhat
> distasteful. ("We can improve your template errors! Just don't write
> templates. Here are some concept maps for your trouble." ;-) There's a
> more substantial objection below.
>
> Let me make the analogy with Proto and Spirit explicit. The tree of
> std::pairs is like a Proto tree. The IntValued is like the SpiritParser
> concept (ill-named because the raw Proto tree is not yet a Spirit
> parser), and sum_ints is the Proto transform that traverses the tree
> and
> turns it into a Spirit parser---a necessarily domain-specific
> algorithm.
>
> The "more substantial" objection is that the tree traversal is now done
> in the domain-specific part of the library. In Proto, the traversal is
> done by Proto itself, which provides various traversal strategies (more
> coming eventually, I hope). Traversal of a Proto tree is actually a
> very
> tricky problem---branch selection is driven by (sub-)grammar
> matching---so Proto is the best place for the traversal logic to live.
> On the face of it, it looks like this is incompatible with your
> solution
> above, which would require the traversal logic to live in Spirit's
> concept maps. Kind of hand-wavy, I know, but have I made my concerns
> understood?
>
> Another potential problem is the building of Proto expression trees.
> The
> operator overloads that build Proto trees also live in Proto. If I were
> to concept-ify Proto, I imagine I'd want to define the ProtoExpr
> concept
> (forgive my poor syntax, I hope intent is clear):
>
> concept ProtoExpr<typename T>
> {
> // A ProtoExpr has a + operator that returns
> // a new ProtoExpr
>
> template<ProtoExpr RHS>
> ProtoExpr T::operator+(T, RHS);
>
> template<ProtoExpr LHS>
> ProtoExpr T::operator+(LHS, T);
>
> // ... and so-on for all the other operators.
> }
Let us just assume Proto only has binary plus and terminals. Adding in the other operators is more of the same.
concept Proto<typename T> { } // trivial
concept ProtoBinaryPlus<typename L, typename R>
{
requires Proto<L> && Proto<R>;
Proto result_type;
result_type operator+ (L, R);
}
>
> This makes ProtoExpr's totally general, as they are today. Now, third
> party code can use ProtoExpr to constrain their templates...
>
> template<ProtoExpr A, ProtoExpr B>
> void add_expressions(A const &a, B const &b)
> {
> a + b; // OK
> }
The general case function add_expressions as provided by Proto (which would capture any type that satisfies the requirements) is:
template <Proto L, Proto R>
requires ProtoBinaryPlus<L, R>
void add_expressions(L const& l, R const& r)
{
a + b; // ok; general case
}
> But when type-checking this template, all we know about a+b is that it
> is a ProtoExpr. If we want to sum_ints, for instance:
>
> template<ProtoExpr A, ProtoExpr B>
> void add_expressions(A const &a, B const &b)
> {
> sum_ints( a + b ); // ERROR: a+b is not IntValued
> }
This is the user's specialization of the Proto library; it is just *one* way of specifying the constraints on the expression. The set of constraints on the following code is a refinement of the general Proto "add_expressions" function.
template <Proto L, Proto R>
requires ProtoBinaryPlus<L, R> && IntVal<L> && IntVal<R>
void add_expressions(L const& l, R const& r)
{
sum_ints( a + b ); // ok; specialization added by the user
}
Perhaps I don't really understand your problem well enough? I'm assuming the first "add_expressions" function is the general function that Proto provides. The second "add_expressions" function is a user-defined functions.
> The body of add_expressions won't type-check because the compiler can't
> verify that a+b is in fact IntValued.
>
> Building expression trees generically and traversing/manipulating them
> generically in concept-ified code is always where my reasoning breaks
> down. Thoughts?
>
> --
> Eric Niebler
> BoostPro Computing
> http://www.boostpro.com
> _______________________________________________
> Unsubscribe & other changes:
> http://lists.boost.org/mailman/listinfo.cgi/boost
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk