...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
As an example of how to create a concept checking class template, we look at how to create the corresponding checks for the InputIterator concept. The complete definition is here:
template <class X> struct InputIterator : Assignable<X>, EqualityComparable<X> { private: typedef std::iterator_traits<X> t; public: typedef typename t::value_type value_type; typedef typename t::difference_type difference_type; typedef typename t::reference reference; typedef typename t::pointer pointer; typedef typename t::iterator_category iterator_category; BOOST_CONCEPT_ASSERT((SignedInteger<difference_type>)); BOOST_CONCEPT_ASSERT((Convertible<iterator_category, std::input_iterator_tag>)); BOOST_CONCEPT_USAGE(InputIterator) { X j(i); // require copy construction same_type(*i++,v); // require postincrement-dereference returning value_type X& x = ++j; // require preincrement returning X& } private: X i; value_type v; // Type deduction will fail unless the arguments have the same type. template <typename T> void same_type(T const&, T const&); };
First, as a convention we name the concept checking class after the concept. Next, since InputIterator is a refinement of Assignable and EqualityComparable, we derive its concept checking class from the checking classes for those other concepts. The library will automatically check for conformance to Assignable and EqualityComparable whenever it checks the InputIterator concept.
Next, we declare the concept's associated types
as member typedefs. The associated difference type is required to be a
signed integer, and the iterator category has to be convertible to
std::input_iterator_tag, so we assert those relationships. The syntax for
accessing associated types through the concept-checking template mirrors
the proposed
syntax for associated type access in C++0x Finally, we use the
BOOST_CONCEPT_USAGE
macro to declare the function that
exercises all the concept's valid expressions. Note that at this point you
may sometimes need to be a little creative: for example, to check that
*i++
returns the iterator's value type, we pass both values to
the same_type
member function template, which requires both
arguments to have the same type, modulo references and cv-qualification.
It's an imperfect check, but it's better than nothing.
You may be wondering why we declared i
and v
as data members in the example above. Why didn't we simply write the
following?
BOOST_CONCEPT_USAGE(InputIterator) { X i; // create the values we need value_type v; X j(i); // require copy construction same_type(*i++,v); // require postincrement-dereference returning value_type X& x = ++j; // require preincrement returning X& }
Unfortunately, that code wouldn't have worked out so well, because it
unintentionally imposes the requirement that X
and its value
type are both default-constructible. On the other hand, since instances of
the InputIterator
template will never be constructed, the
compiler never has to check how its data members will be constructed (C++
Standard Section 14.7.1 9). For that reason you should always
declare values needed for usage patterns as data members.
These sorts of errors in concept definitions can be detected by the use of Concept Archetypes, but it's always better to avoid them pre-emptively.
This library's syntaxes for concept refinement and for access of associated types mirrors the corresponding proposed syntaxes in C++0x. However, C++0x will use “signatures” rather than usage patterns to describe the valid operations on types participating in a concept, so when converting your concept checking classes into language-supported concepts, you'll need to translate your usage function into a series of signatures.
Next: Concept Covering and
Archetypes
Prev: Using Concept
Checks
Copyright © 2000 | Jeremy Siek(jsiek@osl.iu.edu) Andrew Lumsdaine(lums@osl.iu.edu), 2007 David Abrahams. |