enable_if
Copyright 2003 Jaakko Järvi, Jeremiah Willcock, Andrew Lumsdaine.
1 Introduction
The enable_if family of templates is a set of tools to allow a function template or a class template specialization
to include or exclude itself from a set of matching functions or specializations
based on properties of its 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 templates 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 <class Cond, class T = void> struct enable_if;
template <class Cond, class T = void> struct disable_if;
template <class Cond, class T> struct lazy_enable_if;
template <class Cond, class T> struct lazy_disable_if;
template <bool B, class T = void> struct enable_if_c;
template <bool B, class T = void> struct disable_if_c;
template <bool B, class T> struct lazy_enable_if_c;
template <bool B, class T> struct lazy_disable_if_c;
}
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 templates are tools for controlled creation of the SFINAE
conditions.
2 The enable_if templates
The names of the enable_if templates have three parts: an optional lazy_ tag,
either enable_if or disable_if, and an optional _c tag.
All eight combinations of these parts are supported.
The meaning of the lazy_ tag is described in Section 3.3.
The second part of the name indicates whether a true condition argument should
enable or disable the current overload.
The third part of the name indicates whether the condition argument is a bool value
(_c suffix), or a type containing a static bool constant named value (no suffix).
The latter version interoperates with Boost.MPL.
The definitions of enable_if_c and enable_if are as follows (we use enable_if templates
unqualified but they are in the boost namespace).
template <bool B, class T = void>
struct enable_if_c {
typedef T type;
};
template <class T>
struct enable_if_c<false, T> {};
template <class Cond, class T = void>
struct enable_if : public enable_if_c<Cond::value, T> {};
An instantiation of the enable_if_c 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_c<B, T>::type is either a valid or an invalid type
expression, depending on the value of B.
When valid, enable_if_c<B, T>::type equals T.
The enable_if_c template can thus be used for controlling when functions are considered for
overload resolution and when they are 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_c<boost::is_arithmetic<T>::value, T>::type
foo(T t) { return t; }
The disable_if_c template is provided as well, and has the
same functionality as enable_if_c except for the negated condition. The following
function is enabled for all non-arithmetic types.
template <class T>
typename disable_if_c<boost::is_arithmetic<T>::value, T>::type
bar(T t) { return t; }
For easier syntax in some cases and interoperation with Boost.MPL we provide versions of
the enable_if templates taking any type with a bool member constant named
value as the condition argument.
The MPL bool_, and_, or_, and not_ templates are likely to be
useful for creating such types. Also, the traits classes in the Boost.Type_traits library
follow this convention.
For example, the above example function foo can be alternatively written as:
template <class T>
typename enable_if<boost::is_arithmetic<T>, 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 in the previous section could also be written
as:
template <class T>
T foo(T t, typename enable_if<boost::is_arithmetic<T> >::type* dummy = 0);
Hence, an extra parameter of type void* is added, but it is given
a default value to keep the parameter hidden from client code.
Note that the second template argument was not given to enable_if, as the default
void gives the desired behavior.
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:
-
Operators have a fixed number of arguments, thus enable_if must be used in the return type.
- Constructors and destructors do not have a return type; an extra argument is the only option.
- There does not seem to be a way to specify an enabler for a conversion operator. Converting constructors,
however, can have enablers as extra default arguments.
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> >::type> { ... };
template <class T>
class A<T, typename enable_if<is_float<T> >::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 again, the second argument to enable_if is not needed; 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>, void>::type
foo(T t) {}
template <class T>
typename enable_if<boost::is_arithmetic<T>, 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
In some cases it is necessary to avoid instantiating part of a
function signature unless an enabling condition is true. For example:
template <class T, class U> class mult_traits;
template <class T, class U>
typename enable_if<is_multipliable<T, U>, 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 lazy_enable_if
and lazy_disable_if templates (and their _c versions) can be used in such
situations:
template<class T, class U>
typename lazy_enable_if<is_multipliable<T, U>, mult_traits<T, U> >::type
operator*(const T& t, const U& u) { ... }
The second argument of lazy_enable_if must be a class type
that defines a nested type named type whenever the first
parameter (the condition) is true.
Note
Referring to one member type or static constant in a traits class
causes all of the members (type and static constant) of that
specialization to be instantiated. Therefore, if your traits classes
can sometimes contain invalid types, you should use two distinct
templates for describing the conditions and the type mappings. In the
above example, is_multipliable<T, U>::value defines when
mult_traits<T, U>::type is valid.
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>
typename enable_if<boost::is_arithmetic<T>, T>::type
foo(T t);
template <class T>
typename disable_if<boost::is_arithmetic<T>, T>::type
foo(T t);
Two workarounds can be applied:
-
Use an extra dummy parameter which disambiguates the functions. Use a default value for
it to hide the parameter from the caller. For example:
template <int> struct dummy { dummy(int) {} };
template <class T>
typename enable_if<boost::is_arithmetic<T>, T>::type
foo(T t, dummy<0> = 0);
template <class T>
typename disable_if<boost::is_arithmetic<T>, T>::type
foo(T t, dummy<1> = 0);
- Define the functions in different namespaces and bring them into a common
namespace with using declarations:
namespace A {
template <class T>
typename enable_if<boost::is_arithmetic<T>, T>::type
foo(T t);
}
namespace B {
template <class T>
typename disable_if<boost::is_arithmetic<T>, T>::type
foo(T t);
}
using A::foo;
using B::foo;
Note that the second workaround above cannot be used for member
templates. On the other hand, operators do not accept extra arguments,
which makes the first workaround unusable. As the net effect,
neither of the workarounds are of assistance for templated operators that
need to be defined as member functions (assignment and
subscript operators).
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 Frank Pfennig and Yannis Smaragdakis, editors, Generative
Programming and Component Engineering, volume 2830 of LNCS, pages
228--244. Springer Verlag, September 2003.
- [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.