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.
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.
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.
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 ); };
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.
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).
The proposal is a pure extension.
--end