...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
This documentation is out-of-date; similar but
newer implementation techniques are now used. This documentation
also refers to components and protocols in the library's old
interface such as BOOST_CLASS_REQUIRES
and constraints()
functions, which are still supported
but deprecated.
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 constraints() 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 RandomAccessIteratorConcept { 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 Iter> void stable_sort(Iter first, Iter last) { function_requires< RandomAccessIteratorConcept<Iter> >(); ... }
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 <class Concept> 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 BOOST_CLASS_REQUIRE macro which can be used inside the body of a class definition (whereas function_requires() can only be used inside of a function body). This macro 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. We use the type_var and concept names in the nested class and typedef names to help prevent name collisions.
#define BOOST_CLASS_REQUIRE(type_var, ns, concept) \ typedef void (ns::concept <type_var>::* func##type_var##concept)(); \ template <func##type_var##concept _Tp1> \ struct concept_checking_##type_var##concept { }; \ typedef concept_checking_##type_var##concept< \ BOOST_FPTR ns::concept<type_var>::constraints> \ concept_checking_typedef_##type_var##concept
In addition, there are versions of BOOST_CLASS_REQUIRE that take more arguments, to handle concepts that include interactions between two or more types. BOOST_CLASS_REQUIRE was not used in the implementation of the BCCL concept checks because some compilers do not implement template parameters of function pointer type.
Next: Reference
Prev: Programming With
Concepts
Copyright © 2000 | Jeremy Siek(jsiek@osl.iu.edu) Andrew Lumsdaine(lums@osl.iu.edu), 2007 David Abrahams. |