Document number: N1696=04-0136
Programming Language C++, Evolution Working Group
 
Peter Dimov <pdimov@mmltd.net>
 
September 07, 2004

Language Support for Restricted Templates

Introduction

It has been discovered that in the current language it is possible to selectively enable function template overloads and class template partial specializations based on arbitrary compile-time integral constant expressions. This paper proposes that the library solution, usually named enable_if, should be replaced by core language support in order to improve the syntax and readability of the idiom and remove its outstanding limitations.

Motivation

The need to enable certain overloads based on arbitrary compile-time conditions is best illustrated by the following well-known example from the standard library:

explicit vector(size_type n, const T& value = T(), const Allocator& = Allocator());

template <class InputIterator> vector(InputIterator first, InputIterator last, const Allocator& = Allocator());

Since input iterator types do not have a distinguished shape that can be used by the template deduction mechanism, we are left with the general second overload that does not place any requirements on its InputIterator parameter, and is sometimes selected for expressions such as

vector<int> v( 5, 1 ); // five ints with value 1

As a result, the standard library specification explicitly needs to deal with this case in 23.1.1/9 ([lib.sequence.reqmts]/9).

It is possible to "abuse" the "Substitution Failure Is Not An Error" (SFINAE) principle in order to selectively disable the general overload when InputIterator is an integral type, as follows:

template<bool Condition, class R = void> struct enable_if {};
template<class R> struct enable_if<true, R> { typedef R type; };

template <class InputIterator> vector(
    InputIterator first, InputIterator last, const Allocator& = Allocator(),
    typename enable_if< !is_integral<InputIterator>::value >::type * = 0
);

The enable_if utility can also be used in the return type, if there is one:

template<class E> typename enable_if< is_expression<E>::value, negate_expression<E> >::type operator! ( E const & e );

More information and references on enable_if are provided in the documentation of boost::enable_if, available online at http://boost.org/libs/utility/enable_if.html.

Proposed Syntax

The author proposes that function declarations and partial specialization declarations be extended to support a trailing if( expr ), as in the following examples:

template <class InputIterator>
    vector( InputIterator first, InputIterator last, const Allocator& = Allocator() )
    if( !is_integral<InputIterator>::value );

template<class E>
    negate_expression<E> operator! ( E const & e )
    if( is_expression<E>::value );

template<class V>
    struct hash<V>
    if( is_integral<V>::value )
{
    size_t operator()(V v) const { return static_cast<size_t>( v ); }
};

In addition to "sweetening" the syntax compared to the library-based approach, this change also allows conversion operators to be selectively enabled.

Alternative Syntaxes

The following alternative syntax has been proposed (by Howard Hinnant):

template < class InputIterator: !is_integral<InputIterator>::value >
    vector( InputIterator first, InputIterator last, const Allocator& = Allocator() );

Compared to the proposed syntax, it has the drawback of closely associating the condition with a template parameter. The problems with this approach are twofold. First, the condition may refer to the relationship of several template parameters:

template <class A, class B>
    void f( A const & a, B const & b )
    if( is_convertible<A, B>::value || is_convertible<B, A>::value );

Second, the conditional declaration may have no template parameters:

template <class T> class X
{
public:

    void f() if( is_integral<T>::value );
};

template <class T, class P> class smart_ptr
{
public:

    smart_ptr( T * p ) if( P::enable_implicit_ctor );
    explicit smart_ptr( T * p ) if( !P::enable_implicit_ctor );
    operator T* () const if( P::enable_conversion_operator );
};

Concepts

This proposal is much less ambitious than the Concepts proposals [N1510] [N1522] [N1536]. It does seem similar at first sight and its obvious applications are, indeed, restricting overly general template parameters to match certain classes of arguments for which the built-in pattern matching cannot be applied. Therefore, the extension presented here will be able to be used as a poor man's substitution of the Concept mechanism, if the latter is not accepted. However, it is not intended to replace, but to complement.

Concepts express capabilities (the operations a concept supports), and can be used in a function template body to check it statically for errors. They also do not require explicit support from the user in order to match a type. In contrast, conditional declarations express restrictions and can be used to selectively toggle function declarations and partial specializations based on an arbitrary compile-time condition. The two mechanisms are substantially different at their core and each one can solve problems the other cannot. In addition, restricting a template on the relationship of two parameters can avoid specifying a separate templated concept for this purpose whenever appropriate, i.e. when this concept is not essential for the definition and is only being introduced to express a restriction, not a capability.

It is also worth noting that the proposed extension should be substantially easier to implement, because it is a slight modification of an existing library-based mechanism.

Proposed Text

This preliminary paper does not propose any specific wording. It is intended to solicit feedback and guidance from the Evolution Working Group as to whether the approach outlined above has potential and should be pursued any further. Future revisions will include proposed changes to ISO/IEC 14882:2003(E).

Impact on Existing Code

The proposal is a pure extension.

--end