
(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