Synergies between Contract Programming, Concepts and Static Assertions

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.

Table of Contents

1   Synergies with Static Assertions

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:

  1. static invariant { constant-expression ; }
  2. precondition { static constant-expression ; }

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.)

1.1   Alternatives

Imagine for a moment that static invariant was allowed to appear in

  • namespace scope
  • class scope
  • block scope

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.)

2   Synergies with Concepts

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:

  1. We can specify both syntactic and semantic requirements
  2. We can put the contract on the concept and hence avoid to repeat it on each implementation of a concept.

2.1   Concept notation

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:

  • pseudo-signatures, and
  • usage patterns.

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.

2.2   Concept assertions

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.

3   Synergies with Concepts and Static Assertions

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.

4   Conclusion

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.

5   Acknowledgements

The authors would like to thank James Widman for his feedback.