9.9. Explicitly Managing the Overload SetSometimes, CRTP is inadequate for limiting the reach of generalized function template arguments. For example, we may want our function template to operate on built-in types (which cannot have base classes), or on existing third-party types that we don't want to modify. Fortunately, if we can determine the appropriateness of an argument type at compile time, Boost's enable_if family of templates will allow us to manage the overload set non-intrusively. For example, the following function template applies only to iterators over arithmetic types. The examples in this section use boost::iterator_value, a metafunction that retrieves an iterator's value_type. #include <iterator> #include <boost/utility/enable_if.hpp> #include <boost/type_traits/is_arithmetic.hpp> #include <boost/iterator/iterator_traits.hpp> template <class Iterator> typename boost::enable_if< boost::is_arithmetic< // enabling condition typename boost::iterator_value<Iterator>::type > , typename // return type boost::iterator_value<Iterator>::type >::type sum(Iterator start, Iterator end) { typename boost::iterator_value<Iterator>::type x(0); for (;start != end; ++start) x += *start; return x; } If the ::value of the enabling condition C is TRue, enable_if<C,T>::type will be T, so sum just returns an object of Iterator's value_type. Otherwise, sum simply disappears from the overload resolution process! We'll explain why it disappears in a moment, but to get a feeling for what that means, consider this: If we try to call sum on iterators over non-arithmetic types, the compiler will report that no function matches the call. If we had simply written std::iterator_traits<Iterator>::value_type in place of enable_if<...>:: type, calling sum on iterators whose value_type is std:: vector<int> would fail inside sum where it attempts to use operator+=. If the iterators' value_type were std::string, it would actually compile cleanly, but possibly with an undesired result. This technique really becomes interesting when there are function overloads in play. Because sum has been restricted to appropriate arguments, we can now add an overload that will allow us to sum all the arithmetic elements of vector<vector<int> > and other nested containers of arithmetic types.
// given an Iterator that points to a container, get the
// value_type of that container's iterators.
template <class Iterator>
struct inner_value
: boost::iterator_value<
typename boost::iterator_value<Iterator>::type::iterator
>
{};
template <class Iterator>
typename boost::lazy_disable_if<
boost::is_arithmetic< // disabling condition
typename boost::iterator_value<Iterator>::type
>
, inner_value<Iterator> // result metafunction
>::type
sum(Iterator start, Iterator end)
{
typename inner_value<Iterator>::type x(0);
for (;start != end; ++start)
x += sum(start->begin(), start->end());
return x;
}
The word "disable" in lazy_disable_if indicates that the function is removed from the over-load set when the condition is satisfied. The word "lazy" means that the function's result ::type is the result of calling the second argument as a nullary metafunction.[8]
Note that inner_value<Iterator> can only be invoked if Iterator's value type is another iterator. Otherwise, there will be an error when it fails to find the inner (non-)iterator's value type. If we tried to compute the result type greedily, there would be error during overload resolution whenever Iterator's value type turned out to be an arithmetic type and not another iterator. Now let's take a look at how the magic works. Here's the definition of enable_if: template <bool, 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 : enable_if_c<Cond::value, T> {}; Notice that when C is false, enable_if_c<C,T>::type doesn't exist! The C++ standard's overload resolution rules (section 14.8.3) say that when a function template's argument deduction fails, it contributes nothing to the set of candidate functions considered for a given call, and it does not cause an error.[9] This principle has been dubbed "Substitution Failure Is Not An Error" (SFINAE) by David Vandevoorde and Nicolai Josuttis [VJ02].
![]() |