...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
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; }
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 were 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.
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; }
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.
#include <boost/type_traits/is_arithmetic.hpp> #include <boost/type_traits/is_pointer.hpp> #include <boost/utility/enable_if.hpp> class test { public: // A constructor that works for any argument list of size 10 template< class... T , typename boost::enable_if_c< sizeof...( T ) == 10, int >::type = 0 > test( T&&... ); // A conversion operation that can convert to any arithmetic type template< class T , typename boost::enable_if< boost::is_arithmetic< T >, int >::type = 0 > operator T() const; // A conversion operation that can convert to any pointer type template< class T , typename boost::enable_if< boost::is_pointer< T >, int >::type = 0 > operator T() const; }; int main() { // Works test test_( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ); // Fails as expected test fail_construction( 1, 2, 3, 4, 5 ); // Works by calling the conversion operator enabled for arithmetic types int arithmetic_object = test_; // Works by calling the conversion operator enabled for pointer types int* pointer_object = test_; // Fails as expected struct {} fail_conversion = test_; }
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.
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.
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.
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.
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:
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);
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).
Copyright Jaakko Järvi^{*}, Jeremiah Willcock^{*}, Andrew Lumsdaine^{*}, Matt Calabrese
{jajarvi|jewillco|lums}@osl.iu.edu, rivorus@gmail.com
^{*}Indiana University
Open Systems Lab
Use, modification and distribution are subject to the
Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt
or copy at
http://www.boost.org/LICENSE_1_0.txt
).
This document was translated from L^{A}T_{E}X by H^{E}V^{E}A.