diff --git a/bibliography.htm b/bibliography.htm new file mode 100644 index 0000000..554a3a5 --- /dev/null +++ b/bibliography.htm @@ -0,0 +1,70 @@ + + +
+Copyright © 2000 | +Jeremy Siek, Univ.of Notre Dame (jsiek@lsc.nd.edu) + |
+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. + +
+Jeremy Siek +contributed this library. X managed the formal review. + +
+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.. + +
+ 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. + +
+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) + |
+ 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 @@ +
+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 @@ + +
+ 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+ +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. + ++ void function_requires() + { + void (Concept::*x)() = BOOST_FPTR Concept::constraints; + ignore_unused_variable_warning(x); + } +
+ 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 @@ + +
+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 @@ + +
+ template <class Concept> + void function_requires(); ++ +
+ 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 +
+ 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; ++ +
+ 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; ++ +
+ 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 {; ++ +
+ 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; ++ + +
+ 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; ++ +
+ 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; ++ +
+ 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; ++ +
+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