Boost logo

Boost :

Subject: Re: [boost] [guidelines] why template errors suck
From: David Abrahams (dave_at_[hidden])
Date: 2010-09-28 02:14:51


At Mon, 27 Sep 2010 21:06:05 -0400,
Eric Niebler wrote:
>
> On 9/27/2010 6:35 PM, David Abrahams wrote:
> > At Mon, 27 Sep 2010 18:09:16 -0400, Eric Niebler wrote:
> >> And yet nobody can tell me how to actually use concepts to implement
> >> my libraries and improve error messages.
> >
> > I know you know the generic programming process, since we taught a
> > course on it together. You have to start at the beginning and
> > discover the concepts by looking at concrete algorithms, lifting out
> > common requirements, and clustering those into concepts. If that
> > doesn't work in your problem domain, concepts might not be an
> > appropriate mechanism for what you want to. But I wouldn't draw any
> > conclusions about what's possible without first trying it.
>
> I think so far I have failed to make my use case sufficiently clear, so
> I'll simplify and try to follow the Process. Take the following simple
> algorithm sum_ints:
>
> template<class First, class Second>
> int sum_ints_impl( std::pair<First, Second> const & p )
> {
> return sum_ints_impl(p.first) + sum_ints_impl(p.second);
> }
>
> int sum_ints_impl( int i )
> {
> return i;
> }
>
> template<class T>
> int sum_ints( T const & t )
> {
> return sum_ints_impl( t );
> }
>
> Should be self explanatory. sum_ints accepts ints or std::pairs of
> ints, or std::pairs of std::pairs of ints, etc. Now, I'd like to
> concept-check the argument to sum_ints. What can I say about it? It
> must be something I can pass to sum_ints_impl. That means it must be
> an int or a std::pair. How shall I define this int-or-std::pair
> concept?

Naturally, it's hard to do the Process justice with one tiny example
here... but it doesn't seem right to define a concept that must have a
particular type structure, any more than you'd define a concept that
must be either type A or type B. It's like you didn't lift the
abstraction far enough. I guess one test of a proper concept is that
it should be possible for someone to create a "new" type that models
it (for some reasonable definition of "new" that doesn't include a
slightly different instantiation of the same class template).

> So I look for requirements. Let's say there is a concept Integer that is
> satisfied for type int. Let's also say we define a concept Pair that
> checks for the presence of members "first" and "second". Finally, let's
> define a concept "SumIntsAble"---that is the concept that must be
> satisfied by the argument to the sum_ints algorithm. To me, it seems
> like SumIntsAble<T> is "Integer<T> || Pair<T>". (Actually, it's more
> complicated than that because we must also check that Pair's first and
> second members are also SumIntsAble, but let's skip that.)

No need to skip it. 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);
    }
  }

> There is no such thing as OR constraints, but as you say we can fake it
> with concept maps.

"Faking it" is the wrong way to look at it; you're still banging your
head against that wall, brother. I hope you're wearing a helmet, at
least!

If you're "doing generic programming," you pretty much never get to
the point of wanting to constrain something to be "either this or
that." The whole point is to identify a class of types that has a
meaning, like IntValued above (which still isn't really as general as
it should be, but let's leave it there for now). You end up with a
class of types that's *open for extension*.

I suppose you could artificially wall off that class of types by not
documenting IntValued, but then what would it mean to a user when the
error message says, "sorry, that type doesn't model IntValued"? Not
much, IMO. So then you could change the name of IntValued to
something like MustBeAnIntOrAPairOfIntsOrAPairOfPairsOrEtc, and then
you'd be right back in the land of MPL_ASSERT_MESSAGE, yuck! If you
want to reap the benefits of concepts, you have to work with them, not
against 'em.

> As a result, the concept SumIntsAble takes the intersection of the
> two concepts Integer and Pair. Which is nothing interesting. We
> wouldn't be able to call the first sum_ints_impl because that
> function requires "first" and "second" data members (which aren't
> present in the Integer concept). And we can't call the second
> because that requires that the parameter is convertible to int
> (which Pair does not enforce).
>
> I'm stuck.
>
> Can you show me how you would approach this problem?

Does the above help?

hoping-erics-head-doesn't-hurt-too-much-ly y'rs,

-- 
Dave Abrahams
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