enable_if



Copyright 2003 Jaakko Järvi, Jeremiah Willcock, Andrew Lumsdaine.

1  Introduction

The enable_if template is a tool for selectively including functions into the overload resolution set based on the properties of the template arguments. For example, one can define function templates that are only enabled for, and thus only match, an arbitrary set of types defined by a traits class. The enable_if template can also be applied to enable class template specializations. Applications of enable_if are discussed in length in [1] and [2].

1.1  Synopsis

namespace boost {
  template <bool B, class T = void> struct enable_if;
  template <bool B, class T = void> struct disable_if;
  template <bool B, class T> struct enable_if_lazy;
  template <bool B, class T> struct disable_if_lazy;
}

1.2  Background

Sensible operation of template function overloading in C++ relies on the SFINAE (substitution-failure-is-not-an-error) principle [3]: if an invalid argument or return type is formed during the instantiation of a function template, the instantiation is removed from the overload resolution set instead of causing a compilation error. The following example, taken from [1], demonstrates why this is important:

int negate(int i) { return -i; }

template <class F>
typename F::result_type negate(const F& f) { return -f(); }

Suppose the compiler encounters the call negate(1). The first definition is obviously a better match, but the compiler must nevertheless consider (and instantiate the prototypes) of both definitions to find this out. Instantiating the latter definition with F as int would result in:

int::result_type negate(const int&);

where the return type is invalid. If this was an error, adding an unrelated function template (that was never called) could break otherwise valid code. Due to the SFINAE principle the above example is not, however, erroneous. The latter definition of negate is simply removed from the overload resolution set.

The enable_if template is a tool for controlled creation of the SFINAE conditions.

2  The enable_if template

The definition of enable_if is as follows:

template <bool B, class T = void>
struct enable_if {
  typedef T type;
};

template <class T>
struct enable_if<false, T> {};

An instantiation of the enable_if template with the parameter B as true contains a member type type, defined to be T. If B is false, no such member is defined. Thus enable_if<B, T>::type is either a valid or an invalid type expression, depending on the value of B. When valid, enable_if<B, T>::type equals T. The enable_if can thus be used for controlling when functions are considered for overload resolution and when not. For example, the following function is defined for all arithmetic types (according to the classification of the Boost type_traits library):

template <class T>
typename enable_if<boost::is_arithmetic<T>::value, T>::type foo(T t) { return t; }

The disable_if template is provided as well, and has the same functionality as enable_if except for the negated condition. The following function is enabled for all non-arithmetic types.

template <class T>
typename disable_if<boost::is_arithmetic<T>::value, T>::type foo(T t) { return t; }

3  Using enable_if

The enable_if templates are defined in boost/utility/enable_if.hpp, which is included by boost/utility.hpp.

The enable_if template can be used either as the return type, or as an extra argument. For example, the foo function below could also be written as:

template <class T>
typename T foo(T t, typename enable_if<boost::is_arithmetic<T> >::type* dummy = 0); 

Hence, an extra argument of type void* is added, but it is given a default value to keep the argument hidden from the client code. Note that the type parameter was not given to enable_if, the default void gives the desired type.

Whether to write the enabler as an argument or within the return type is largely a matter of taste, but for certain functions, only one alternative is possible:

3.1  Enabling template class specializations

Class template specializations can be enabled or disabled with enable_if. One extra template parameter needs to be added for the enabler expressions. This parameter has the default value void. For example:

template <class T, class Enable = void> 
class A { ... };

template <class T>
class A<T, typename enable_if<is_integral<T>::value>::type> { ... };

template <class T>
class A<T, typename enable_if<is_float<T>::value>::type> { ... };

Instantiating A with any integral type matches the first specialization, whereas any floating point type matches the second one. All other types match the primary template. The condition can be any compile-time boolean expression that depends on the template arguments of the class. Note that no type parameter is given to enable_if; the default (void) is the correct value.

3.2  Overlapping enabler conditions

Once the compiler has examined the enabling conditions and included the function into the overload resolution set, normal C++ overload resolution rules are used to select the best matching function. In particular, there is no ordering between enabling conditions. Function templates with enabling conditions that are not mutually exclusive can lead to ambiguities. For example:

template <class T>
typename enable_if<boost::is_integral<T>::value, void>::type foo(T t) {}

template <class T>
typename enable_if<boost::is_arithmetic<T>::value, void>::type foo(T t) {}

All integral types are also arithmetic. Therefore, say, for the call foo(1), both conditions are true and both functions are thus in the overload resolution set. They are both equally good matches and thus ambiguous. Of course, more than one enabling condition can be simultaneously true as long as other arguments disambiguate the functions.

The above discussion applies to using enable_if in class template partial specializations as well.

3.3  Lazy enable_if

The standard is not entirely clear on how deep into nested template instantiations SFINAE applies, and there is variation among compilers. For example:

template <class T, class U> class mult_traits;

template <class T, class U>
typename enable_if<is_multipliable<T, U>::value, typename mult_traits<T, U>::type>::type
operator*(const T& t, const U& u) { ... }

Assume the class template mult_traits is a traits class defining the resulting type of a multiplication operator. The is_multipliable traits class specifies for which types to enable the operator. Whenever is_multipliable<A, B>::value is true for some types A and B, then mult_traits<A, B>::type is defined.

Now, trying to invoke (some other overload) of operator* with, say, operand types C and D for which is_multipliable<C, D>::value is false and mult_traits<C, D>::type is not defined is an error on some compilers. The SFINAE principle is not applied because the invalid type occurs as an argument to another template. The enable_if_lazy and disable_if_lazy templates can be used in such situations:

template<class T, class U>
typename enable_if_lazy<is_multipliable<T, U>::value, mult_traits<T, U> >::type
operator*(const T& t, const U& u) { ... }

The second argument of enable_if_lazy must be a class type that defines a nested type named type whenever the first parameter (the condition) is true.

3.4  Compiler workarounds

Some compilers flag functions as ambiguous if the only distinguishing factor is a different condition in an enabler (even though the functions could never be ambiguous). For example, some compilers (e.g. GCC 3.2) diagnose the following two functions as ambiguous:

template <class T> struct dummy { dummy(int) {} };

template <class T>
typename enable_if<boost::is_arithmetic<T>::value, T>::type foo(T t);

template <class T>
typename enable_if<!boost::is_arithmetic<T>::value, T>::type foo(T t);

Two workarounds can be applied:

4  Acknowledgements

We are grateful to Howard Hinnant, Jason Shirk, Paul Mensonides and Richard Smith whose findings have influenced the library.

References

[1]
Jaakko Järvi, Jeremiah Willcock, Howard Hinnant, and Andrew Lumsdaine. Function overloading based on arbitrary properties of types. C/C++ Users Journal, 21(6):25--32, June 2003.

[2]
Jaakko Järvi, Jeremiah Willcock, and Andrew Lumsdaine. Concept-controlled polymorphism. In Proceedings of Generative Programming and Component Engineering (GPCE'03), September 2003. To appear.

[3]
David Vandevoorde and Nicolai M. Josuttis. C++ Templates: The Complete Guide. Addison-Wesley, 2002.

Contributed by:
Jaakko Järvi, Jeremiah Willcock and Andrew Lumsdaine
{jajarvi|jewillco|lums}@osl.iu.edu
Indiana University
Open Systems Lab
This document was translated from LATEX by HEVEA.