C++ Boost


Concept Checking

header boost/concept_checks.hpp

The C++ language does not provide direct support for ensuring that template arguments meet the requirements demanded of them by the generic algorithm or template class. This means that if the user makes an error, the resulting compiler error will point to somewhere deep inside the implementation of the generic algorithm, giving an error that may not be easy to match with the cause.

We have developed a method for forcing the compiler to give better error messages. The idea is to exercise all the requirements placed on the template arguments at the very beginning of the generic algorithm. We have created some macros and a methodology for how to do this.

Suppose we wish to add concept checks to the STL copy() algorithm, which has the following prototype.

  template <class InIter, class OutIter>
  OutIter copy(InIter first, InIter last, OutIter result);
We will need to make sure the InIter is a model of InputIterator and that OutIter is a model of OutputIterator. The first step is to create the code that will exercise the expressions associated with each of these concepts. The following is the concept checking class for OutputIterator. The iterator objects i and j are data members of the concept checking class which would normally require the Iter type to be DefaultConstructible. However, we never create instances of concept checking classes so this requirement is not induced.

  template <class Iter>
  struct OutputIterator {
    CLASS_REQUIRES(Iter, Assignable);
    void constraints() {
      (void)*i;        // require dereference operator
      ++i;             // require preincrement operator
      i++;             // require postincrement operator
      *i++ = *j;       // require postincrement and assignment
    }
    Iter i, j;
  };

Once the concept checking classes are complete, one simply needs to invoke them at the beginning of the generic algorithm using our REQUIRE macro. Here's what the STL copy() algorithm looks like with the concept checks inserted.

  template <class InIter, class OutIter>
  OutIter copy(InIter first, InIter last, OutIter result)
  { 
    REQUIRE(OutIter, OutputIterator); 
    REQUIRE(InIter, InputIterator); 
    return copy_aux(first, last, result, VALUE_TYPE(first)); 
  }

Looking back at the OutputIterator concept checking class, you might wonder why we used the CLASS_REQUIRES macro instead of just REQUIRE. The reason for this is that different tricks are needed to force compilation of the checking code when invoking the macro from inside a class definition instead of a function.

For the most part, the user does not need to know how to create concept checks and insert them in generic algorithms, however it is good to know what they are. This will make the error messages coming from libraries that use concept checks easier to understand. Also if you are unsure about whether you can use a certain class with a particular algorithm, or whether some custom-made class of your own is compatible, a good first check is to invoke the appropriate concept checker.

Here's a quick example program that one could write to see if the types adjacency_list and edge_list really model the concepts they claim to.

  #include <boost/graph/graph_concepts.hpp>
  #include <boost/graph/adjacency_list.hpp>
  #include <boost/graph/edge_list.hpp>

  using namespace boost;

  int main(int,char*[])
  {
    typedef adjacency_list<listS,vecS,bidirectionalS> AdjList;
    REQUIRE(AdjList, VertexAndEdgeListGraph);
    REQUIRE(AdjList, BidirectionalGraph);

    typedef std::pair<int, int> E;
    typedef edge_list<E*, E, int> EdgeList;
    REQUIRE(EdgeList, EdgeListGraph);

    return 0;
  }

If you create your own generic algorithms we highly encourage you to use and publish the corresponding concept checks.


Copyright © 2000 Jeremy Siek, Univ.of Notre Dame (jsiek@lsc.nd.edu)
Lie-Quan Lee, Univ.of Notre Dame (llee1@lsc.nd.edu)
Andrew Lumsdaine, Univ.of Notre Dame (lums@lsc.nd.edu)