Boost logo

Boost :

Subject: Re: [boost] [guidelines] why template errors suck
From: John Bytheway (jbytheway+boost_at_[hidden])
Date: 2010-09-26 14:24:05


On 25/09/10 04:51, Eric Niebler wrote:
> On 9/24/2010 9:37 PM, David Abrahams wrote:
>> The real challenge would be making it easy to write new concepts. Right now the usage requirements are simple to state, but if you wanted static_assert to fire,
>> we'd need to use checks that don't cause errors, e.g. instead of:
>>
>> same_type(*i++,v); // postincrement-dereference returning value_type
>>
>>
>> you'd have to write something like:
>>
>> static_assert(
>> has_postincrement<InputIterator>::value, "not postincrementable");
>> static_assert(
>> has_postincrement_deref<InputIterator>::value, "not dereferenceable");
>> static_assert(
>> is_same<
>> postincrement_deref<InputIterator>::type, InputIterator::value_type
>> >::value,
>> "postincrement dereference doesn't return value_type");
>>
>> Well, in all, I guess I like the rigor but it's not very compatible with the loosey-goosey C++03 way of specifying concepts <pines for real concept support>.
>
> Blech. Can C++0x SFINAE on expressions make this any nicer? The idea is
> to be able to text expressions for well-formedness without causing a
> compile error. Then we'd need a way to chain these expressions to make
> lists of requirements. Maybe PP sequences consisting of valid
> expressions? Is there a compiler that implements this yet so we can play
> with it?

I decided to have a first stab at something along these lines (with g++
4.4 in C++0x mode). I think my experiment shows promise. I tweaked a
cut-down version of the Boost.ConceptCheck InputIterator concept thus:

template<typename X>
class InputIterator {
  private:
    typedef std::iterator_traits<X> t;
  public:
    typedef typename t::value_type value_type;
    ~InputIterator()
    {
      verify::check(verify::same_type(*i++, v));
    }
  private:
    verify::example<X> i;
    verify::example<value_type> v;
};

Here verify::same_type, i, and v are all Proto terminals, so the
expression verify::same_type(*i++, v) is a Proto expression tree. the
verify::check function traverses this tree and pulls out the first
problematic thing and causes a pertinent static_assertion to fail about
it. So, for example, using the following classes:

struct A {
  typedef std::random_access_iterator_tag iterator_category;
  typedef int value_type;
  typedef ptrdiff_t difference_type;
  typedef int* pointer;
  typedef int& reference;
};

struct B : A {
  B operator++(int);
};

struct C : A {
  C operator++(int);
  double const& operator*();
};

struct D : A {
  D operator++(int);
  int const& operator*();
};

and checking each of them with a statement like:

VERIFY_MODELS_CONCEPT(InputIterator<A>);

yields the following compiler errors, which I think are pretty succinct
and pertinent:

For A:

../../verify/ensure_postincrementable.hpp: In destructor
‘verify::not_postincrementable_problem<ShouldBePostIncrementable>::~not_postincrementable_problem()
[with ShouldBePostIncrementable = A]’:
inputiterator.hpp:17: instantiated from
‘InputIterator<X>::~InputIterator() [with X = A]’
../../verify/models_concept.hpp:13: instantiated from ‘void
verify::models_concept() [with Concept = InputIterator<A>]’
check-a.cpp:8: instantiated from here
../../verify/ensure_postincrementable.hpp:13: error: static assertion
failed: "given type should be postincremantable but is not"

For B:

../../verify/ensure_dereferencable.hpp: In destructor
‘verify::not_dereferencable_problem<ShouldBeDereferencable>::~not_dereferencable_problem()
[with ShouldBeDereferencable = B]’:
inputiterator.hpp:17: instantiated from
‘InputIterator<X>::~InputIterator() [with X = B]’
../../verify/models_concept.hpp:13: instantiated from ‘void
verify::models_concept() [with Concept = InputIterator<B>]’
check-b.cpp:8: instantiated from here
../../verify/ensure_dereferencable.hpp:9: error: static assertion
failed: "given type should be dereferencable but is not"

For C:

../../verify/ensure_same_type.hpp: In destructor
‘verify::different_type_problem<ShouldBeSame1,
ShouldBeSame2>::~different_type_problem() [with ShouldBeSame1 = double,
ShouldBeSame2 = int]’:
inputiterator.hpp:17: instantiated from
‘InputIterator<X>::~InputIterator() [with X = C]’
../../verify/models_concept.hpp:13: instantiated from ‘void
verify::models_concept() [with Concept = InputIterator<C>]’
check-c.cpp:8: instantiated from here
../../verify/ensure_same_type.hpp:9: error: static assertion failed:
"given types should be the same but are not"

For D there is no error because everything is valid.

I think the first two errors here are as good as what David was
suggesting above. The last is not so good because it lacks the context
to see where these two types (int and double) arose from. This
particular example is probably OK given that it points you to the
relevant line in the concept definition, but it's something to watch out
for as things become more complicated.

Of course, this particular implementation is not obviously better (and
is possibly worse) than the existing Boost.ConceptCheck techniques. But
I just wanted to prove the concept; I think it should be possible to
define concepts using a Proto DSEL (and no doubt an unhealthy dose of
preprocessor nonsense) and to use concepts so defined both to assert
that types model concepts (giving helpful compile errors when they
don't) and to test whether types model concepts for function dispatch,
etc. without compiler errors. Probably it would be better to try to
mimic the proposed standard syntax in this DSEL rather than
Boost.ConceptCheck's syntax (although it would be nice to ease migration
from Boost.ConceptCheck too).

Obviously there are some tricky aspects (two that spring to mind are how
to handle named associated types or member functions and getting const
correctness right) but I think it's doable.

Does this sound like a worthwhile thing to attempt? I'd be willing to
give it a try.

John Bytheway


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