C++ Boost

header boost/concept_checks.hpp and boost/concept_archetypes.hpp

Generic programming in C++ is characterized by the use of template parameters to represent abstract data types (or ``concepts''). However, the C++ language itself does not provide a mechanism for explicitly handling concepts. As a result, it can be difficult to insure that a concrete type meets the requirements of the concept it is supposed to represent. Error messages resulting from incorrect use of a concrete type can be particularly difficult to decipher. The Boost Concept Checking Library provides mechanisms for checking parameters in C++ template libraries. The mechanisms use standard C++ and introduce no run-time overhead. The main cost of using the mechanism is in compile-time. The documentation is organized into the following sections.

  1. Introduction
  2. Motivating Example
  3. Using Concept Checks
  4. Creating Concept Checking Classes
  5. Concept Covering and Archetypes
  6. Programming With Concepts
  7. Implementation
  8. Reference
    1. Functions
    2. Classes
    3. Basic Concept Checking Classes
    4. Iterator Concept Checking Classes
    5. Function Object Concept Checking Classes
    6. Container Concept Checking Classes
    7. Basic Archetype Classes
    8. Iterator Archetype Classes
    9. Function Object Archetype Classes
    10. Container Archetype Classes
  9. History
  10. Publications
  11. Acknowledgements

Jeremy Siek contributed this library. X managed the formal review.

Introduction

A concept is a set of requirements (valid expressions, associated types, semantic invariants, complexity guarantees, etc.) that a type must fulfill to be correctly used as arguments in a call to a generic algorithm. In C++, concepts are represented by formal template parameters to function templates (generic algorithms). However, C++ has no explicit mechanism for representing concepts --- template parameters are merely placeholders. By convention, these parameters are given names corresponding to the concept that is required, but a C++ compiler does not enforce compliance to the concept when the template parameter is bound to an actual type.

Naturally, if a generic algorithm is invoked with a type that does not fulfill at least the syntactic requirements of the concept, a compile-time error will occur. However, this error will not per se reflect the fact that the type did not meet all of the requirements of the concept. Rather, the error may occur deep inside the instantiation hierarchy at the point where an expression is not valid for the type, or where a presumed associated type is not available. The resulting error messages are largely uninformative and basically impenetrable.

What is required is a mechanism for enforcing ``concept safety'' at (or close to) the point of instantiation. The Boost Concept Checking Library uses some standard C++ constructs to enforce early concept compliance and that provides more informative error messages upon non-compliance.

Note that this technique only addresses the syntactic requirements of concepts (the valid expressions and associated types). We do not address the semantic invariants or complexity guarantees, which are also part of concept requirements..

Motivating Example

We present a simple example to illustrate incorrect usage of a template library and the resulting error messages. In the code below, the generic std::stable_sort() algorithm from the Standard Template Library (STL)[3, 4,5] is applied to a linked list.
  bad_error_eg.cpp:
   1  #include <list>
   2  #include <algorithm>
   3
   4  struct foo {
   5    bool operator<(const foo&) const { return false; }
   6  };
   7  int main(int, char*[]) {
   8    std::list<foo> v;
   9    std::stable_sort(v.begin(), v.end());
  10    return 0;
  11  }
Here, the std::stable_sort() algorithm is prototyped as follows:
  template <class RandomAccessIterator>
  void stable_sort(RandomAccessIterator first, RandomAccessIterator last);
Attempting to compile this code with Gnu C++ produces the following compiler error. The output from other compilers is listed in the Appendix.
stl_algo.h: In function `void __merge_sort_loop<_List_iterator
  <foo,foo &,foo *>, foo *, int>(_List_iterator<foo,foo &,foo *>,
  _List_iterator<foo,foo &,foo *>, foo *, int)':
stl_algo.h:1448:   instantiated from `__merge_sort_with_buffer
  <_List_iterator<foo,foo &,foo *>, foo *, int>(
   _List_iterator<foo,foo &,foo *>, _List_iterator<foo,foo &,foo *>,
   foo *, int *)'
stl_algo.h:1485:   instantiated from `__stable_sort_adaptive<
  _List_iterator<foo,foo &,foo *>, foo *, int>(_List_iterator
  <foo,foo &,foo *>, _List_iterator<foo,foo &,foo *>, foo *, int)'
stl_algo.h:1524:   instantiated from here
stl_algo.h:1377: no match for `_List_iterator<foo,foo &,foo *> & -
  _List_iterator<foo,foo &,foo *> &'
In this case, the fundamental error is that std:list::iterator does not model the concept of RandomAccessIterator. The list iterator is only bidirectional, not fully random access (as would be a vector iterator). Unfortunately, there is nothing in the error message to indicate this to the user.

To a C++ programmer having enough experience with template libraries the error may be obvious. However, for the uninitiated, there are several reasons why this message would be hard to understand.

  1. The location of the error, line 9 of bad_error_eg.cpp is not pointed to by the error message, despite the fact that Gnu C++ prints up to 4 levels deep in the instantiation stack.
  2. There is no textual correlation between the error message and the documented requirements for std::stable_sort() and for RandomAccessIterator.
  3. The error message is overly long, listing functions internal to the STL that the user does not (and should not!) know or care about.
  4. With so many internal library functions listed in the error message, the programmer could easily infer that the error is due to the library, rather than to his or her own code.
The following is an example of what we might expect from a more informative message (and is in fact what the Boost Concept Checking Library produces):
concept_checks.hpp: In method `void LessThanComparable_concept
  <_List_iterator<foo,foo &,foo *> >::constraints()':
concept_checks.hpp:334:   instantiated from `RandomAccessIterator_concept
  <_List_iterator<foo,foo &,foo *> >::constraints()'
bad_error_eg.cpp:9:   instantiated from `stable_sort<_List_iterator
  <foo,foo &,foo *> >(_List_iterator<foo,foo &,foo *>, 
  _List_iterator<foo,foo &,foo *>)'
concept_checks.hpp:209: no match for `_List_iterator<foo,foo &,foo *> &
  < _List_iterator<foo,foo &,foo *> &'
This message rectifies several of the shortcomings of the standard error messages.

Using Concept Checks

For each concept there is a concept checking class which can be used to make sure that a given type (or set of types) models the concept. The Boost Concept Checking Library includes concept checking classes for all of the concepts used in the C++ standard library and a few more. The Reference section below lists these concept checking classes. In addition, other boost libraries come with concept checking classes for the concepts that are particular to those libraries. An example of one of these classes is the EqualityComparableConcept class.
  template <class T> struct EqualityComparableConcept;
Each concept checking class has a member function named constraints() which contains the valid expressions for the concept. To check whether some type, say foo, is EqualityComparable, we need to instantiate the concept checking class with foo: EqualityComparableConcept<foo> and then find a way to get the compiler to compile the constraints() function without actually calling it. The Boost Concept Checking Library defines two utilities that make this easy: function_requires() and BOOST_CLASS_REQUIRES. function_requires() function can be used in function bodies and the BOOST_CLASS_REQUIRES macro can be used inside class bodies. The function_requires() function takes no arguments, but has a template parameter for the concept checking class. This means that the instantiated concept checking class must be given as an explicit template argument, as shown below.
  void some_function_using_foo() {
    function_requires< EqualityComparableConcept<foo> >();
    // ...
  };
The BOOST_CLASS_REQUIRES macro can be used inside a class definition to check whether some type models a concept.
  struct some_class_using_foo {
    BOOST_CLASS_REQUIRES(foo, EqualityComparableConcept);
  };
To add concept checks to the std::stable_sort() function the library implementor would simply insert function_requires() at the top of std::stable_sort() to make sure the template parameter type models RandomAccessIterator. In addition, std::stable_sort() requires that the value_type of the iterators be LessThanComparable, so we also use function_requires() to check this.
  template <class RandomAccessIter>
  void stable_sort(RandomAccessIter first, RandomAccessIter last)
  {
    function_requires< RandomAccessIteratorConcept<RandomAccessIter> >();
    typedef typename std::iterator_traits<RandomAccessIter>::value_type value_type;
    function_requires< LessThanComparableConcept<value_type> >();
    ...
  }

Some concepts deal with more than one type. In this case the corresponding concept checking class will have multiple template parameters. The following example shows how function_requires() is used with the ReadWritePropertyMap concept which takes two type parameters: a property map and the key type for the map.

  template <class IncidenceGraph, class Buffer, class BFSVisitor, 
            class ColorMap>
  void breadth_first_search(IncidenceGraph& g, 
    typename graph_traits<IncidenceGraph>::vertex_descriptor s, 
    Buffer& Q, BFSVisitor vis, ColorMap color)
  {
    typedef typename graph_traits<IncidenceGraph>::vertex_descriptor Vertex;
    function_requires< ReadWritePropertyMap<ColorMap, Vertex> >();
    ...
  }
As an example of using class_requires we look at a concept check that could be added to std::vector. One requirement that is placed on the element type is that it must be Assignable. We can check this by inserting class_requires<AssignableConcept<T> > at the top of the definition for std::vector.
  namespace std {
    template <class T>
    struct vector {
      typedef typename class_requires< AssignableConcept<T> >::check req;
      ...
    };
  }
Although the concept checks are designed for use by generic library implementors, they can also be useful to end users. Sometimes one may not be sure whether some type models a particular concept. This can easily be checked by creating a small program and using function_requires() with the type and concept in question. The file stl_concept_checks.cpp gives and example of applying the concept checks to STL containers. The file is listed here:
  #include <boost/concept_checks.hpp>

  #include <iterator>
  #include <set>
  #include <map>
  #include <vector>
  #include <list>
  #include <deque>

  int
  main()
  {
    typedef std::vector<int> Vector;
    typedef std::deque<int> Deque;
    typedef std::list<int> List;

    function_requires< Mutable_RandomAccessContainer<Vector> >();
    function_requires< BackInsertionSequence<Vector> >();

    function_requires< Mutable_RandomAccessContainer<Deque> >();
    function_requires< FrontInsertionSequence<Deque> >();
    function_requires< BackInsertionSequence<Deque> >();

    function_requires< Mutable_ReversibleContainer<List> >();
    function_requires< FrontInsertionSequence<List> >();
    function_requires< BackInsertionSequence<List> >();

    typedef std::set<int> Set;
    typedef std::multiset<int> MultiSet;
    typedef std::map<int,int> Map;
    typedef std::multimap<int,int> MultiMap;

    function_requires< SortedAssociativeContainer<Set> >();
    function_requires< SimpleAssociativeContainer<Set> >();
    function_requires< UniqueAssociativeContainer<Set> >();

    function_requires< SortedAssociativeContainer<MultiSet> >();
    function_requires< SimpleAssociativeContainer<MultiSet> >();
    function_requires< MultipleAssociativeContainer<MultiSet> >();

    function_requires< SortedAssociativeContainer<Map> >();
    function_requires< UniqueAssociativeContainer<Map> >();
    function_requires< PairAssociativeContainer<Map> >();

    function_requires< SortedAssociativeContainer<MultiMap> >();
    function_requires< MultipleAssociativeContainer<MultiMap> >();
    function_requires< PairAssociativeContainer<MultiMap> >();

    return 0;
  }

Creating Concept Checking Classes

As an example of how to create a concept checking class, we look at how to create the corresponding checks for the RandomAccessIterator concept. First, as a convention we name the concept checking class after the concept, and add the suffix ``_concept''. Note that the REQUIRE macro expects the suffix to be there. Next we must define a member function named constraints() in which we will exercise the valid expressions of the concept. The REQUIRE macro expects this function's signature to appear exactly as it is appears below: a void non-const member function with no parameters.

The first part of the constraints() function includes the requirements that correspond to the refinement relationship between RandomAccessIterator and the concepts which it builds upon: BidirectionalIterator and LessThanComparable. We could have instead used CLASS_REQUIRES and placed these requirements in the class body, however CLASS_REQUIRES uses C++ language features that are less portable.

Next we check that the iterator_category of the iterator is either std::random_access_iterator_tag or a derived class. After that we write out some code that corresponds to the valid expressions of the RandomAccessIterator concept. Typedefs can also be added to enforce the associated types of the concept.

  template <class Iter>
  struct RandomAccessIterator_concept
  {
    void constraints() {
      function_requires< BidirectionalIteratorConcept<Iter> >();
      function_requires< LessThanComparableConcept<Iter> >();
      function_requires< ConvertibleConcept<
        typename std::iterator_traits<Iter>::iterator_category,
        std::random_access_iterator_tag> >();

      i += n;
      i = i + n; i = n + i;
      i -= n;
      i = i - n;
      n = i - j;
      i[n];
    }
    Iter a, b;
    Iter i, j;
    typename std::iterator_traits<Iter>::difference_type n;
  };
}
One potential pitfall in designing concept checking classes is using more expressions in the constraint function than necessary. For example, it is easy to accidentally use the default constructor to create the objects that will be needed in the expressions (and not all concepts require a default constructor). This is the reason we write the constraint function as a member function of a class. The objects involved in the expressions are declared as data members of the class. Since objects of the constraints class template are never instantiated, the default constructor for the concept checking class is never instantiated. Hence the data member's default constructors are never instantiated (C++ Standard Section 14.7.1 9).

Concept Covering and Archetypes

We have discussed how it is important to select the minimal requirements (concepts) for the inputs to a component, but it is equally important to verify that the chosen concepts cover the algorithm. That is, any possible user error should be caught by the concept checks and not let slip through. Concept coverage can be verified through the use of archetype classes. An archetype class is an exact implementation of the interface associated with a particular concept. The run-time behavior of the archetype class is not important, the functions can be left empty. A simple test program can then be compiled with the archetype classes as the inputs to the component. If the program compiles then one can be sure that the concepts cover the component. The following code shows the archetype class for the TrivialIterator concept. Some care must be taken to ensure that the archetype is an exact match to the concept. For example, the concept states that the return type of operator*() must be convertible to the value type. It does not state the more stringent requirement that the return type be T& or const T&. That means it would be a mistake to use T& or const T& for the return type of the archetype class. The correct approach is to create an artificial return type that is convertible to T, as we have done here with input_proxy. The validity of the archetype class test is completely dependent on it being an exact match with the concept, which must be verified by careful (manual) inspection.
  template <class T>
  struct input_proxy {
    operator T() { return t; }
    static T t;
  };
  template <class T>
  class trivial_iterator_archetype
  {
    typedef trivial_iterator_archetype self;
  public:
    trivial_iterator_archetype() { }
    trivial_iterator_archetype(const self&) { }
    self& operator=(const self&) { return *this;  }
    friend bool operator==(const self&, const self&) { return true; }
    friend bool operator!=(const self&, const self&) { return true; }
    input_proxy<T> operator*() { return input_proxy<T>(); }
  };

  namespace std {
    template <class T>
    struct iterator_traits< trivial_iterator_archetype<T> >
    {
      typedef T value_type;
    };
  }
Generic algorithms are often tested by being instantiated with a number of common input types. For example, one might apply std::stable_sort() with basic pointer types as the iterators. Though appropriate for testing the run-time behavior of the algorithm, this is not helpful for ensuring concept coverage because C++ types never match particular concepts, they often provide much more than the minimal functionality required by any one concept. That is, even though the function template compiles with a given type, the concept requirements may still fall short of covering the functions actual requirements. This is why it is important to compile with archetype classes in addition to testing with common input types.

The following is an excerpt from stl_concept_covering.cpp that shows how archetypes can be used to check the requirement documentation for std::stable_sort(). In this case, it looks like the CopyConstructible and Assignable requirements were forgotten in the SGI STL documentation (try removing those archetypes). The Boost archetype classes have been designed so that they can be layered. In this example the value type of the iterator is composed out of three archetypes. In the archetype class reference below, template parameters named Base indicate where the layered archetype can be used.

  {
    typedef less_than_comparable_archetype< 
      copy_constructible_archetype<
        assignable_archetype<> > > ValueType;
    random_access_iterator_archetype<ValueType> ri;
    std::stable_sort(ri, ri);
  }

Programming with Concepts

The process of deciding how to group requirements into concepts and deciding which concepts to use in each algorithm is perhaps the most difficult (yet most important) part of building a generic library. A guiding principle to use during this process is one we call the requirement minimization principle.

Requirement Minimization Principle: Minimize the requirements on the input parameters of a component to increase its reusability.

There is natural tension in this statement. By definition, the input parameters must be used by the component in order for the component to accomplish its task (by ``component'' we mean a function or class template). The challenge then is to implement the component in such a way that makes the fewest assumptions (the minimum requirements) about the inputs while still accomplishing the task.

The traditional notions of abstraction tie in directly to the idea of minimal requirements. The more abstract the input, the fewer the requirements. Thus, concepts are simply the embodiment of generic abstract data types in C++ template programming.

When designing the concepts for some problem domain it is important to keep in mind their purpose, namely to express the requirements for the input to the components. With respect to the requirement minimization principle, this means we want to minimize concepts.

It is important to note, however, that minimizing concepts does not mean simply reducing the number of valid expressions in the concept. For example, the std::stable_sort() function requires that the value type of the iterator be LessThanComparable, which not only includes operator<(), but also operator>(), operator<=(), and operator>=(). It turns out that std::stable_sort() only uses operator<(). The question then arises: should std::stable_sort() be specified in terms of the concept LessThanComparable or in terms of a concept that only requires operator<()?

We remark first that the use of LessThanComparable does not really violate the requirement minimization principle because all of the other operators can be trivially implemented in terms of operator<(). By ``trivial'' we mean one line of code and a constant run-time cost. More fundamentally, however, the use of LessThanComparable does not violate the requirement minimization principle because all of the comparison operators (<, >, <=, >=) are conceptually equivalent (in a mathematical sense). Adding conceptually equivalent valid expressions is not a violation of the requirement minimization principle because no new semantics are being added --- only new syntax. The added syntax increases re-usability.

For example, the maintainer of the std::stable_sort() may some day change the implementation in places to use operator>() instead of operator<(), since, after all, they are equivalent. Since the requirements are part of the public interface, such a change could potentially break client code. If instead LessThanComparable is given as the requirement for std::stable_sort(), then the maintainer is given a reasonable amount of flexibility within which to work.

Minimality in concepts is a property associated with the underlying semantics of the problem domain being represented. In the problem domain of basic containers, requiring traversal in a single direction is a smaller requirement than requiring traversal in both directions (hence the distinction between ForwardIterator and BidirectionalIterator). The semantic difference can be easily seen in the difference between the set of concrete data structures that have forward iterators versus the set that has bidirectional iterators. For example, singly-linked lists would fall in the set of data structures having forward iterators, but not bidirectional iterators. In addition, the set of algorithms that one can implement using only forward iterators is quite different than the set that can be implemented with bidirectional iterators. Because of this, it is important to factor families of requirements into rather fine-grained concepts. For example, the requirements for iterators are factored into the six STL iterator concepts (trivial, output, input, forward, bidirectional, and random access).

Implementation

Ideally we would like to catch, and indicate, the concept violation at the point of instantiation. As mentioned in D&E[2], the error can be caught by exercising all of the requirements needed by the function template. Exactly how the requirements (the valid expressions in particular) are exercised is a tricky issue, since we want the code to be compiled --- but not executed. Our approach is to exercise the requirements in a separate function that is assigned to a function pointer. In this case, the compiler will instantiate the function but will not actually invoke it. In addition, an optimizing compiler will remove the pointer assignment as ``dead code'' (though the run-time overhead added by the assignment would be trivial in any case). It might be conceivable for a compiler to skip the semantic analysis and compilation of the constraints function in the first place, which would make our function pointer technique ineffective. However, this is unlikely because removal of unnecessary code and functions is typically done in later stages of a compiler. We have successfully used the function pointer technique with GNU C++, Microsoft Visual C++, and several EDG-based compilers (KAI C++, SGI MIPSpro). The following code shows how this technique can be applied to the std::stable_sort() function:
  template <class RandomAccessIterator>
  void stable_sort_constraints(RandomAccessIterator i) {
    typename std::iterator_traits<RandomAccessIterator>
      ::difference_type n;
    i += n;  // exercise the requirements for RandomAccessIterator
    ...
  }
  template <class RandomAccessIterator>
  void stable_sort(RandomAccessIterator first, RandomAccessIterator last) {
    typedef void (*fptr_type)(RandomAccessIterator);
    fptr_type x = &stable_sort_constraints;
    ...
  }
There is often a large set of requirements that need to be checked, and it would be cumbersome for the library implementor to write constraint functions like stable_sort_constraints() for every public function. Instead, we group sets of valid expressions together, according to the definitions of the corresponding concepts. For each concept we define a concept checking class template where the template parameter is for the type to be checked. The class contains a contraints() member function which exercises all of the valid expressions of the concept. The objects used in the constraints function, such as n and i, are declared as data members of the concept checking class.
  template <class Iter>
  struct RandomAccessIterator_concept {
    void constraints() {
      i += n;
      ...
    }
    typename std::iterator_traits<RandomAccessIterator>
      ::difference_type n;
    Iter i;
    ...
  };
We can still use the function pointer mechanism to cause instantiation of the constraints function, however now it will be a member function pointer. To make it easy for the library implementor to invoke the concept checks, we wrap the member function pointer mechanism in a function named function_requires(). The following code snippet shows how to use function_requires() to make sure that the iterator is a RandomAccessIterator.
  template <class RandomAccessIter>
  void stable_sort(RandomAccessIter first, RandomAccessIter last)
  {
    function_requires< RandomAccessIteratorConcept >();
    ...
  }
The definition of the function_requires() is as follows. The Concept is the concept checking class that has been instantiated with the modeling type. We assign the address of the constraints member function to the function pointer x, which causes the instantiation of the constraints function and checking of the concept's valid expressions. We then assign x to x to avoid unused variable compiler warnings, and wrap everything in a do-while loop to prevent name collisions.
  template 
  void function_requires()
  {
    void (Concept::*x)() = BOOST_FPTR Concept::constraints;
    ignore_unused_variable_warning(x);
  }
To check the type parameters of class templates, we provide the class_requires class which can be used inside the body of a class definition (whereas function_requires() can only be used inside of a function body). class_requires declares a nested class template, where the template parameter is a function pointer. We then use the nested class type in a typedef with the function pointer type of the constraint function as the template argument.
  template <class Concept>
  class class_requires
  {
    typedef void (Concept::* function_pointer)();

    template <function_pointer Fptr>
    struct dummy_struct { };
  public:
    typedef dummy_struct< BOOST_FPTR Concept::constraints > check;
  };
class_requires was not used in the implementation of the Boost Concept Checking Library concept checks because several compilers do not implement template parameters of function pointer type.

Reference

Functions

  template <class Concept>
  void function_requires();

Classes

  template <class Concept>
  struct class_requires {
    typedef ... check;
  };
  // Make sure that Type1 and Type2 are exactly the same type.
  // If they are not, then the nested typedef for type will
  // not exist and cause a compiler error.
  template <class Type1, class Type2>
  struct require_same {
    typedef ... type;
  };
  // usage example
  typedef typedef require_same::type req1; // this will compile OK
  typedef typedef require_same::type req1; // this will cause a compiler error

Basic Concept Checking Classes

  template <class T> struct Integer_concept; // Is T a built-in integer type?
  template <class T> struct SignedIntegerConcept; // Is T a built-in signed integer type?
  template <class X, class Y> struct ConvertibleConcept; // Is X convertible to Y?
  template <class T> struct AssignableConcept;
  template <class T> struct DefaultConstructibleConcept;
  template <class T> struct CopyConstructibleConcept;
  template <class T> struct BooleanConcept;
  template <class T> struct EqualityComparableConcept;
  // Is class T equality comparable on the left side with type Left?
  template <class T, class Left> struct LeftEqualityComparableConcept;
  template <class T> struct LessThanComparableConcept;

Iterator Concept Checking Classes

  template <class Iter> struct TrivialIteratorConcept;
  template <class Iter> struct Mutable_TrivialIteratorConcept;
  template <class Iter> struct InputIteratorConcept;
  template <class Iter, class T> struct OutputIteratorConcept;
  template <class Iter> struct ForwardIteratorConcept;
  template <class Iter> struct Mutable_ForwardIteratorConcept;
  template <class Iter> struct BidirectionalIteratorConcept;
  template <class Iter> struct Mutable_BidirectionalIteratorConcept;
  template <class Iter> struct RandomAccessIteratorConcept;
  template <class Iter> struct Mutable_RandomAccessIteratorConcept;

Function Object Concept Checking Classes

  template <class Func, class Return> struct GeneratorConcept;
  template <class Func, class Return, class Arg> struct UnaryFunctionConcept;
  template <class Func, class Return, class First, class Second> struct BinaryFunctionConcept;
  template <class Func, class Arg> struct UnaryPredicateConcept;
  template <class Func, class First, class Second> struct BinaryPredicateConcept;
  template <class Func, class First, class Second> struct Const_BinaryPredicateConcept {;

Container Concept Checking Classes

  template <class C> struct ContainerConcept;
  template <class C> struct Mutable_ContainerConcept;

  template <class C> struct ForwardContainerConcept;
  template <class C> struct Mutable_ForwardContainerConcept;

  template <class C> struct ReversibleContainerConcept;
  template <class C> struct Mutable_ReversibleContainerConcept;

  template <class C> struct RandomAccessContainerConcept;
  template <class C> struct Mutable_RandomAccessContainerConcept;

  template <class C> struct SequenceConcept;
  template <class C> struct FrontInsertionSequenceConcept;
  template <class C> struct BackInsertionSequenceConcept;

  template <class C> struct AssociativeContainerConcept;
  template <class C> struct UniqueAssociativeContainerConcept;
  template <class C> struct MultipleAssociativeContainerConcept;
  template <class C> struct SimpleAssociativeContainerConcept;
  template <class C> struct PairAssociativeContainerConcept;
  template <class C> struct SortedAssociativeContainerConcept;

Basic Archetype Classes

  class null_archetype; // A type that models no concepts.
  template <class Base = null_archetype> class default_constructible_archetype;
  template <class Base = null_archetype> class assignable_archetype;
  template <class Base = null_archetype> class copy_constructible_archetype;
  template <class Left, class Base = null_archetype> class left_equality_comparable_archetype;
  template <class Base = null_archetype> class equality_comparable_archetype;
  template <class T, class Base = null_archetype> class convertible_to_archetype;

Iterator Archetype Classes

  template <class ValueType> class trivial_iterator_archetype;
  template <class ValueType> class mutable_trivial_iterator_archetype;
  template <class ValueType> class input_iterator_archetype;
  template <class ValueType> class forward_iterator_archetype;
  template <class ValueType> class bidirectional_iterator_archetype;
  template <class ValueType> class random_access_iterator_archetype;

Function Object Archetype Classes

  template <class Arg, class Return> class unary_function_archetype;
  template <class Arg1, class Arg2, class Return> class binary_function_archetype;
  template <class Arg> class predicate_archetype;
  template <class Arg1, class Arg2> class binary_predicate_archetype;

Container Archetype Classes

UNDER CONSTRUCTION

History

An earlier version of this concept checking system was developed by the author while working at SGI in their C++ compiler and library group. The earlier version is now part of the SGI STL distribution. The boost concept checking library differs from the concept checking in the SGI STL in that the definition of concept checking classes has been greatly simplified, at the price of less helpful verbiage in the error messages.

Publications

Acknowledgements

The idea to use function pointers to cause instantiation is due to Alexander Stepanov. I am not sure of the origin of the idea to use expressions to do up-front checking of templates, but it did appear in D&E[ 2]. Thanks to Matt Austern for his excellent documentation and organization of the STL concepts, upon which these concept checks are based. Thanks to Boost members for helpful comments and reviews.

Copyright © 2000 Jeremy Siek, Univ.of Notre Dame (jsiek@lsc.nd.edu)