Boost logo

Boost :

From: David Abrahams (dave_at_[hidden])
Date: 2006-08-20 23:48:09

"Chad Parry" <spam_at_[hidden]> writes:

> I would like to know if many people in the community think the following
> library would be useful.
> I would like to define concepts using this syntax:
> template<typename left_type, typename right_type = left_type>
> struct less_than_comparable
> {
> static var<left_type const> left;
> static var<right_type const> right;
> static var<bool> b;
> typedef CONCEPTS_USE_PATTERN(b = (left < right)) constraints;
> };

Chad, this looks like really interesting work.

I should point out that I have recently changed the Boost concept
checking library to support a syntax much closer to that of conceptc++
( In

1. you can access associated types through concepts, e.g.:

   template <class I>
   distance(I start, I finish);

2. BOOST_CONCEPT_ASSERT((SomeConcept< ... >)) works in all contexts
   and produces readable error messages.

3. You can refine concepts via inheritance, with no need to explicitly
   assert the base concepts:

  template <class TT>
  struct InputIterator
    : Assignable<TT> // HERE
    , EqualityComparable<TT> // HERE
      // associated types
      typedef typename boost::detail::iterator_traits<TT>::value_type value_type;
      typedef typename boost::detail::iterator_traits<TT>::difference_type difference_type;
      typedef typename boost::detail::iterator_traits<TT>::reference reference;
      typedef typename boost::detail::iterator_traits<TT>::pointer pointer;
      typedef typename boost::detail::iterator_traits<TT>::iterator_category iterator_category;

      BOOST_CONCEPT_ASSERT((Convertible<iterator_category, std::input_iterator_tag>));
      // valid expressions
        TT j(i);
        (void)*i; // require dereference operator
        ++j; // require preincrement operator
        i++; // require postincrement operator
    TT i;

4. You can use "where" clauses on generic functions:

  template<typename RanIter>
      ((LessThanComparable<typename Mutable_RandomAccessIterator<RanIter>::value_type>))
    , (void))

I think there are probably other features I'm forgetting; it's been a
few months ;-)

> In the code above the less_than_comparable<T, U> concept is defined. The
> var<> type is meant to resemble the Var<> in Stroustrup's concept
> proposals.

I don't recommend trying to emulate those proposals unless you mean
the very recent ones written with the authors of conceptc++. The
concept support we get is probably going to look a lot more like
conceptc++ than like the early Stroustrup/Dos Reis proposals.

> The CONCEPTS_USE_PATTERN(EXP) macro expands to BOOST_TYPEOF(EXP). The b,
> left and right variables have lambda-like capabilities where you can apply
> any operation on them and they return a complex type that remembers what
> operations have been applied.
> Functions that want to enforce concepts would use this syntax:
> template<typename operand_type>
> bool my_less_than(operand_type left, operand_type right)
> {
> // Enforce the model.
> typedef less_than_comparable<operand_type> model;
> BOOST_STATIC_ASSERT(is_match<model>::value);

That won't produce a readable error message.

> constrained_proxy<1, model> constrained_left(constrain<1,
> model>(left));
> constrained_proxy<2, model> constrained_right(constrain<2,
> model>(right));
> // Use the constrained proxies just like regular types.
> return (constrained_left < constrained_right);
> }
> Obviously this syntax is more cumbersome than the current Boost Concept
> Check Library's. But it offers more power too:
> 1) The author doesn't have to hand-craft an archetype class! The model
> knows what operations should be allowed and the constrained_proxy<> class
> enforces that only those operations are taken.

Ah, so. The point here is that if you can instantiate my_less_than
with something (anything) without causing an error, my_less_than can
only be using operations allowed by the model, because the function
only really operates on the constrained_proxy. Very clever.

> 2) It is possible to check whether a model is valid without causing a fatal
> error.

I don't believe that can be done in general. There are many usage
patterns (or pseudosignatures as in conceptc++) that *can't* be
checked in C++98 without causing an error. For example, we have no
non-fatal test for default constructibility. How would you handle
that in your system [I see the answer below]?

> That's what the is_match<model> metapredicate is for. This could be
> used to dispatch different optimized code depending on which concepts the
> type is known to model. Now, there are some things that can't be detected
> by library code (like the presence of a default constructor)

Aha! So in what sense does your system make that non-fatal checking
possible where it wasn't possible before? Oh, it encodes the usage
pattern in a type, which is then converted into a metafunction. Very
very clever.

Unless I'm missing something, you can't represent lots of important
usage patterns other than default construction, e.g.:

     std::swap(x, y)


> so I would have to rely on authors to provide hints about those.
> I'm planning on just assuming that any requirements I can't detect
> are fulfilled, and then the compiler will complain if that's wrong.

That defeats one of the main purposes of concept checking: early
detection of errors before a large instantiation backtrace is

> 3) The concepts can be built up from primitives using and_ and or_ and not_
> operations, just like in Stroustrup's proposals. For example, the concept
> above could have included this statement instead: "typedef
> and_<CONCEPTS_USE_PATTERN(b = left < right), CONCEPTS_USE_PATTERN(b = right
> < left), copy_constructible<left> > constraints;"

I think I'd better leave it to Doug Gregor to explain why "or"
constraints are problematic.

> 4) This use pattern syntax more closely resembles the C++0x use pattern
> proposals than any other existing library.

Unless you're looking at the wrong proposals ;-)

Usage patterns don't really work for compiler support of concept
checking, because they hide intermediate types that need to be

> 5) The constrained_proxy class prevents certain errors where a pathological
> type behaves unexpectedly. I'm talking about functions like
> "evil_bool_proxy operator<(mytype, mytype)" where evil_bool_proxy defines
> its own && and || operators. In the expression, "(a < b) && (c < d)" the
> constrained_proxy enforces that the && operation gets applied to bool
> values, not to evil_bool_proxy values.
> 6) This mechanism allows you to express that a model has important semantic
> implications also. The library assumes that a model is valid if it is a
> structural match.
> But you can override that by saying, "template<typename
> value_type> declare_match<forward_iterator<istream_iterator<value_type> > >
> : false_ { };"

That's fine if you're doing it just for checking, but it's not fine
for dispatch. Consider what happens to vector(i1, i2) when i1 and i2
are input iterators that structurally mimic forward iterators.

> 7) Traits classes could potentially be added to this library so that authors
> could express mappings such as that the constrained_proxy should call a free
> function "push_back(my_seq, elem)" instead of the normal "my_seq.push_back
> (elem)" for a given sequence type.
> So far I have gotten var<> and declare_match<> and is_match<> to compile.
> I've also finished and_<>, or_<> and not_<>. I'm working on
> constrained_proxy<>.

Well, it looks really interesting (really!), but I think you should
review the ConceptC++ publications, in particular N1849, before
investing more time in it. Also, IMO, you should try to model the
standard concepts and see how it works out in practice.

Lots of food for thought in your work, for sure.

Dave Abrahams
Boost Consulting

Boost list run by bdawes at, gregod at, cpdaniel at, john at