Boost logo

Boost :

Subject: Re: [boost] [guidelines] why template errors suck
From: Eric Niebler (eric_at_[hidden])
Date: 2010-09-29 12:34:33


(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.
}

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
}

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
}

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

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