diff --git a/bibliography.htm b/bibliography.htm new file mode 100644 index 0000000..554a3a5 --- /dev/null +++ b/bibliography.htm @@ -0,0 +1,70 @@ + + + +Boost Graph Library: Bibliography + +C++ Boost + +
+ + +

Bibliography

+ +
+ +

1 +
Andrei Alexandrescu
+Better Template Error Messages.
+C/C++ Users Journal, March, 1999. + + +

2 +
Bjarne Stroustrup
+Design and Evolution of C++.
+Addison-Wesley, 1994 + +

3 +
+M. H. Austern. +
Generic Programming and the STL. +
Professional computing series. Addison-Wesley, 1999. + +

4 +
+David R. Musser and Atul Saini +
STL Tutorial and Reference Guide. +
Professional computing series. Addison-Wesley, 1996. + +

5 +
+A. A. Stepanov and M. Lee +
The Standard Template Library. +
ISO Programming Language C++ Project, May 1994. +
X3J16/94-0095, WG21/N0482 + + + +
+ +
+
+ + +
Copyright © 2000 +Jeremy Siek, Univ.of Notre Dame (jsiek@lsc.nd.edu) +
+ + + diff --git a/class_concept_check_fail_expected.cpp b/class_concept_check_fail_expected.cpp new file mode 100644 index 0000000..24743d6 --- /dev/null +++ b/class_concept_check_fail_expected.cpp @@ -0,0 +1,30 @@ +// (C) Copyright Jeremy Siek 2000. Permission to copy, use, modify, +// sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. + +#include + +/* + + This file verifies that class_requires of the Boost Concept Checking + Library catches errors when it is suppose to. + +*/ + +struct foo { }; + +using namespace boost; + +class class_requires_test +{ + BOOST_CLASS_REQUIRES(foo, EqualityComparableConcept); +}; + +int +main() +{ + class_requires_test x; + return 0; +} diff --git a/class_concept_check_test.cpp b/class_concept_check_test.cpp new file mode 100644 index 0000000..2eb3cce --- /dev/null +++ b/class_concept_check_test.cpp @@ -0,0 +1,37 @@ +// (C) Copyright Jeremy Siek 2000. Permission to copy, use, modify, +// sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. + +#include + +/* + + This file verifies that the BOOST_CLASS_REQUIRES macro of the Boost + Concept Checking Library does not cause errors when it is not suppose + to. + +*/ + +struct foo { bool operator()(int) { return true; } }; +struct bar { bool operator()(int, char) { return true; } }; + +using namespace boost; + +class class_requires_test +{ + BOOST_CLASS_REQUIRES(int, EqualityComparableConcept); + typedef int* int_ptr; typedef const int* const_int_ptr; + BOOST_CLASS_REQUIRES2(int_ptr, const_int_ptr, Comparable2Concept); + BOOST_CLASS_REQUIRES3(foo, bool, int, UnaryFunctionConcept); + BOOST_CLASS_REQUIRES4(bar, bool, int, char, BinaryFunctionConcept); +}; + +int +main() +{ + class_requires_test x; + ignore_unused_variable_warning(x); + return 0; +} diff --git a/concept_check.htm b/concept_check.htm new file mode 100644 index 0000000..062dd4f --- /dev/null +++ b/concept_check.htm @@ -0,0 +1,228 @@ + + + +Concept Checking + +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 (BCCL) 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. +
  3. Motivating Example
  4. +
  5. Using Concept Checks
  6. +
  7. Creating Concept Checking Classes
  8. +
  9. Concept Covering and Archetypes
  10. +
  11. Programming With Concepts
  12. +
  13. Implementation
  14. +
  15. Reference
  16. +
  17. History
  18. +
  19. Publications
  20. +
  21. Acknowledgements
  22. +
+ +

+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. + + + + + +
+
+ + +
Copyright © 2000 +jsiek@lsc.nd.edu) +
+ + + diff --git a/concept_check_fail_expected.cpp b/concept_check_fail_expected.cpp new file mode 100644 index 0000000..a646746 --- /dev/null +++ b/concept_check_fail_expected.cpp @@ -0,0 +1,23 @@ +// (C) Copyright Jeremy Siek 2000. Permission to copy, use, modify, +// sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. + +#include + +/* + + This file verifies that function_requires() of the Boost Concept + Checking Library catches errors when it is suppose to. + +*/ + +struct foo { }; + +int +main() +{ + boost::function_requires< boost::EqualityComparableConcept >(); + return 0; +} diff --git a/concept_check_test.cpp b/concept_check_test.cpp new file mode 100644 index 0000000..b5fc58b --- /dev/null +++ b/concept_check_test.cpp @@ -0,0 +1,185 @@ +// (C) Copyright Jeremy Siek 2000. Permission to copy, use, modify, +// sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. + +#include +#include + +/* + + This file verifies that the BOOST_FUNCTION_REQUIRES macros of the + Boost Concept Checking Library do not cause errors when they are not + suppose to and verifies that the concept archetypes meet the + requirements of their matching concepts. + +*/ + + +int +main() +{ + using namespace boost; + + //=========================================================================== + // Basic Concepts + { + typedef default_constructible_archetype<> foo; + function_requires< DefaultConstructibleConcept >(); + } + { + typedef assignable_archetype<> foo; + function_requires< AssignableConcept >(); + } + { + typedef copy_constructible_archetype<> foo; + function_requires< CopyConstructibleConcept >(); + } + { + typedef sgi_assignable_archetype<> foo; + function_requires< SGIAssignableConcept >(); + } + { + typedef copy_constructible_archetype<> foo; + typedef convertible_to_archetype convertible_to_foo; + function_requires< ConvertibleConcept >(); + } + { + function_requires< ConvertibleConcept >(); + } + { + typedef equality_comparable_archetype<> foo; + function_requires< EqualityComparableConcept >(); + } + { + typedef less_than_comparable_archetype<> foo; + function_requires< LessThanComparableConcept >(); + } + { + typedef comparable_archetype<> foo; + function_requires< ComparableConcept >(); + } + { + typedef equal_op_first_archetype<> First; + typedef equal_op_second_archetype<> Second; + function_requires< EqualOpConcept >(); + } + { + typedef not_equal_op_first_archetype<> First; + typedef not_equal_op_second_archetype<> Second; + function_requires< NotEqualOpConcept >(); + } + { + typedef less_than_op_first_archetype<> First; + typedef less_than_op_second_archetype<> Second; + function_requires< LessThanOpConcept >(); + } + { + typedef less_equal_op_first_archetype<> First; + typedef less_equal_op_second_archetype<> Second; + function_requires< LessEqualOpConcept >(); + } + { + typedef greater_than_op_first_archetype<> First; + typedef greater_than_op_second_archetype<> Second; + function_requires< GreaterThanOpConcept >(); + } + { + typedef greater_equal_op_first_archetype<> First; + typedef greater_equal_op_second_archetype<> Second; + function_requires< GreaterEqualOpConcept >(); + } + + { + typedef copy_constructible_archetype<> Return; + typedef plus_op_first_archetype First; + typedef plus_op_second_archetype Second; + function_requires< PlusOpConcept >(); + } + + //=========================================================================== + // Function Object Concepts + + { + typedef generator_archetype > foo; + function_requires< GeneratorConcept > >(); + } + { + function_requires< GeneratorConcept< void_generator_archetype, void > >(); + } + { + typedef unary_function_archetype F; + function_requires< UnaryFunctionConcept >(); + } + { + typedef binary_function_archetype F; + function_requires< BinaryFunctionConcept >(); + } + { + typedef unary_predicate_archetype F; + function_requires< UnaryPredicateConcept >(); + } + { + typedef binary_predicate_archetype F; + function_requires< BinaryPredicateConcept >(); + typedef const_binary_predicate_archetype const_F; + function_requires< Const_BinaryPredicateConcept >(); + } + + //=========================================================================== + // Iterator Concepts + { + typedef trivial_iterator_archetype > Iter; + function_requires< TrivialIteratorConcept >(); + } + { + typedef mutable_trivial_iterator_archetype > Iter; + function_requires< Mutable_TrivialIteratorConcept >(); + } + { + typedef input_iterator_archetype > Iter; + function_requires< InputIteratorConcept >(); + } + { + typedef output_iterator_archetype Iter; + function_requires< OutputIteratorConcept >(); + } + { + typedef forward_iterator_archetype > Iter; + function_requires< ForwardIteratorConcept >(); + } + { + typedef forward_iterator_archetype > Iter; + function_requires< Mutable_ForwardIteratorConcept >(); + } + { + typedef bidirectional_iterator_archetype > Iter; + function_requires< BidirectionalIteratorConcept >(); + } + { + typedef bidirectional_iterator_archetype > Iter; + function_requires< Mutable_BidirectionalIteratorConcept >(); + } + { + typedef random_access_iterator_archetype > Iter; + function_requires< RandomAccessIteratorConcept >(); + } + { + typedef random_access_iterator_archetype > Iter; + function_requires< Mutable_RandomAccessIteratorConcept >(); + } + + //=========================================================================== + // Container Concepts + + { + + function_requires< ContainerConcept< > >(); + } + { + + } + + return 0; +} diff --git a/concept_covering.htm b/concept_covering.htm new file mode 100644 index 0000000..3c4afa3 --- /dev/null +++ b/concept_covering.htm @@ -0,0 +1,96 @@ + +

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);
+  }
+
+ diff --git a/creating_concepts.htm b/creating_concepts.htm new file mode 100644 index 0000000..db5941e --- /dev/null +++ b/creating_concepts.htm @@ -0,0 +1,72 @@ +

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). diff --git a/history.htm b/history.htm new file mode 100644 index 0000000..dcb07b1 --- /dev/null +++ b/history.htm @@ -0,0 +1,29 @@ + +

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. diff --git a/implementation.htm b/implementation.htm new file mode 100644 index 0000000..585e034 --- /dev/null +++ b/implementation.htm @@ -0,0 +1,133 @@ + + +

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. + diff --git a/prog_with_concepts.htm b/prog_with_concepts.htm new file mode 100644 index 0000000..0cc329a --- /dev/null +++ b/prog_with_concepts.htm @@ -0,0 +1,106 @@ + +

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). diff --git a/reference.htm b/reference.htm new file mode 100644 index 0000000..b908214 --- /dev/null +++ b/reference.htm @@ -0,0 +1,152 @@ + +

Reference

+ +
    +
  1. Functions
  2. +
  3. Classes
  4. +
  5. Basic Concept Checking Classes
  6. +
  7. Iterator Concept Checking Classes
  8. +
  9. Function Object Concept Checking Classes
  10. +
  11. Container Concept Checking Classes
  12. +
  13. Basic Archetype Classes
  14. +
  15. Iterator Archetype Classes
  16. +
  17. Function Object Archetype Classes
  18. +
  19. Container Archetype Classes
  20. +
+ +

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
+
+ diff --git a/stl_concept_check.cpp b/stl_concept_check.cpp new file mode 100644 index 0000000..0bcfad2 --- /dev/null +++ b/stl_concept_check.cpp @@ -0,0 +1,90 @@ +// (C) Copyright Jeremy Siek 2000. Permission to copy, use, modify, +// sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. + +// +// This file checks to see if various standard container +// implementations live up to requirements specified in the C++ +// standard. As many implementations do not live to the requirements, +// it is not uncommon for this file to fail to compile. The +// BOOST_HIDE_EXPECTED_ERRORS macro is provided here if you want to +// see as much of this file compile as possible. +// + +#include + +#include +#include +#include +#include +#include +#include +#ifndef BOOST_NO_SLIST +#include +#endif + +//#define BOOST_HIDE_EXPECTED_ERRORS + +int +main() +{ + using namespace boost; + +#if defined(_ITERATOR_) && defined(BOOST_HIDE_EXPECTED_ERRORS) + // VC++ STL implementation is not standard conformant and + // fails to pass these concept checks +#else + typedef std::vector Vector; + typedef std::deque Deque; + typedef std::list List; + + // VC++ missing pointer and const_pointer typedefs + function_requires< Mutable_RandomAccessContainerConcept >(); + function_requires< BackInsertionSequenceConcept >(); + +#if !(defined(__GNUC__) && defined(BOOST_HIDE_EXPECTED_ERRORS)) +#if !(defined(__sgi) && defined(BOOST_HIDE_EXPECTED_ERRORS)) + // old deque iterator missing n + iter operation + function_requires< Mutable_RandomAccessContainerConcept >(); +#endif + // warnings about signed and unsigned in old deque version + function_requires< FrontInsertionSequenceConcept >(); + function_requires< BackInsertionSequenceConcept >(); +#endif + + // VC++ missing pointer and const_pointer typedefs + function_requires< Mutable_ReversibleContainerConcept >(); + function_requires< FrontInsertionSequenceConcept >(); + function_requires< BackInsertionSequenceConcept >(); + +#ifndef BOOST_NO_SLIST + typedef std::slist SList; + function_requires< FrontInsertionSequenceConcept >(); +#endif + + typedef std::set Set; + typedef std::multiset MultiSet; + typedef std::map Map; + typedef std::multimap MultiMap; + + function_requires< SortedAssociativeContainerConcept >(); + function_requires< SimpleAssociativeContainerConcept >(); + function_requires< UniqueAssociativeContainerConcept >(); + + function_requires< SortedAssociativeContainerConcept >(); + function_requires< SimpleAssociativeContainerConcept >(); + function_requires< MultipleAssociativeContainerConcept >(); + + function_requires< SortedAssociativeContainerConcept >(); + function_requires< UniqueAssociativeContainerConcept >(); + function_requires< PairAssociativeContainerConcept >(); + + function_requires< SortedAssociativeContainerConcept >(); + function_requires< MultipleAssociativeContainerConcept >(); + function_requires< PairAssociativeContainerConcept >(); +#endif + + return 0; +} diff --git a/using_concept_check.htm b/using_concept_check.htm new file mode 100644 index 0000000..f935afe --- /dev/null +++ b/using_concept_check.htm @@ -0,0 +1,181 @@ +

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;
+  }
+
+