Author: | Lawrence Crowl and Thorsten Ottosen |
---|---|
Contact: | lawrence.crowl@sun.com and nesotto@cs.aau.dk |
Organizations: | Sun Microsystems and Dezide Aps |
Date: | 2005-08-24 |
Number: | WG21/N1867 and J16/05-0127 |
Working Group: | Evolution |
Abstract
This paper explores the synergies between Contract Programming, Concepts and Static Assertions. Contract Programming and Concepts seem to be quite orthogonal and complement each other whereas Contract Programming and Static Assertions are overlapping proposals.
To compare the details of the two proposals, we refer to the individual papers:
The first proposal suggests the addition of a new keyword static_assert which may be used as in
template <ValueType charT, class traits> class basic_string { static_assert( tr1::is_pod<charT>::value, "Template argument charT must be a POD type in class template basic_string" ); // ... };
Such a statement may appear in
In the Contract Programming proposal, there are currently two mechanisms for static assertions:
The static invariant is allowed to appear at
The second mechanism is only valid within pre- and post-conditions. So the current Contract Programming proposal would not allow static assertions at namespace scope. (Remark: the reader might want to consider the new syntax in the latest revision of the Contract Programming paper, n1866.)
Imagine for a moment that static invariant was allowed to appear in
Then the two proposals would be completely overlapping.
What is left is that Contract Programming still has extra syntax for static assertions inside eg. preconditions. It seems there is yet a proposal that might have an influence on this mechanism:
The above paper introduces the highly desirable notion of constant-valued functions such that all inline functions recursively defined only in terms of constants yield a compile-time constant.
Given such a capability in the compiler, it would make sense to demand constant-valued pre- and postconditions to be evaluated at compile-time. This would remove the syntactic irregularity found in
template< unsigned Size > struct array { template< unsigned N > T& at() precondition { static N < Size; }
which now is
template< unsigned N > T& at() precondition { N < Size; }
The good consequence is of course that a user cannot forget to put the static keyword there---it just works as we would like it to. (Remark: this is exactly what we have done in n1866.)
Concepts are proposed in two different papers:
Both proposals define concepts completely in terms of syntactical requirements.
A concept requirement as specified in the standard includes both syntactic requirements and (operational) semantic requirements. The latter cannot be specified with concepts, but can to a large extend to specified with Contract Programming.
In would be fair to say that Concepts and Contract Programming are largely orthogonal proposals that can be put in the language independently of each other. However, there are major benefits of having both:
The particular notation used to define a concept greatly affects the aesthetics and/or possibility of specifying contracts on Concepts.
Basically two ways of defining concepts are being proposed:
In the former syntax, we could define a concept as follows:
template<typeid T> concept EqualityComparable { bool operator==(T a, T b); bool operator!=(T a, T b); };
It is quite easy and natural to add contracts to this definition:
template<typeid T> concept EqualityComparable { bool operator==(T a, T b) postcondition( result ) { result == !( a != b ); } bool operator!=(T a, T b) postcondition( result ) { result == !( a == b ); } };
Remark: there is no infinite recursion problems here since postconditions do not check contracts.
In the spirit of usage patterns, we would write the concept as:
concept EqualityComparable< class T > { T a; T b; bool( a == b ); bool( a == b ); };
When we try to put contracts into this definition, it looks somewhat different:
concept EqualityComparable< class T > { T a; T b; bool( a == b ) postcondition( result ) { result == !( a != b ); } bool( a == b ) postcondition( result ) { result == !( a == b ); } };
This might not look too bad, but now consider
concept RandomAccessContainer< class T > { T t; const T u; T::size_type s = u.size() postcondition( result ) { if( u.empty() ) result == 0; } T::value_type& v = t[5] precondition { /* how do we refer to the argument here??? */ } };
Thus the usage pattern approach does not mix well with contract programming.
This section discusses potential reuse of keywords.
In the Indiana proposal, the keyword require is used to make an assertion about nested types:
template< typeid X > concept Container { ... require typename X::iterator; require InputIterator<X::iterator>; ... };
It might be ok to reuse static invariant (or simply invariant) here:
template< typeid X > concept Container { ... static invariant { typename X::iterator; InputIterator<X::iterator>; } ... };
Both proposals define ways to explicitly state that a UDT adheres to a particular concept. In the Texas proposal the keyword static_assert is used as follows:
static_assert Forward_iterator<Ptr_to_int>;
where Forward_iterator<Ptr_to_int> can simply be thought of a predicate. It might not be too bad to say
static invariant { Forward_iterator<Ptr_to_int>; }
instead. static invariant has a natural block structure that will make it easier to specify several predicates.
Consider again the fixed-size array class:
template< class T, unsigned Size > struct array { ...
and let us define a function template min_value() which does not work when Size == 0.
With static_assert we might say
template< class T, std::size_t N > const T& min_value( const std::array<T, N>& x ) { static_assert( N > 0, "..." ); ... }
With Contract Programing we might say
template< class T, std::size_t N > const T& min_value(const std::array<T, N>& x) precondition { N > 0; }
The advantage is here that the error will point to the declaration of the function and not the implementation.
With Concepts we might say
template< class T, std::size_t N > where { N > 0 } const T& min_value(const std::array<T, N>& x);
So there is considerable overlap in this example.
The functionality provided by Contract Programming and static_assert completely overlap. The main difference will be that in Contract Programming the compound keyword static invariant (or simply invariant) will be used instead. It is thus possible to save a keyword.
The functionality provided by Concepts and Contract Programming are orthogonal. The two mechanisms complement each other well and if contracts are specified in concepts, the UDT automatically reuses the contracts from the concept.
If concepts are specified with usage patterns, it becomes difficult to attach contracts to them because there is no way to refer to the arguments of function; pseudo-signatures do not have this problem.
In both concept proposals, static invariant (or simply invariant) might be reused to specify compile-time assertions in various contexts.
The authors would like to thank James Widman for his feedback.