Section 9.9.  Explicitly Managing the Overload Set
Team LiB
Previous Section Next Section

9.9. Explicitly Managing the Overload Set

Sometimes, 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]

[8] For completeness, enable_if.hpp includes plain disable_if and lazy_enable_if templates, as well as _C-suffixed versions of all four templates that accept integral constants instead of wrappers as a first argument.

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].

[9] You might be wondering why inner_value and lazy evaluation were needed, while enable_if itself doesn't cause an error. The template argument deduction rules include a clause (14.8.2, paragraph 2) that enumerates conditions under which an invalid deduced type in a function template signature will cause deduction to fail. It turns out that the form used by enable_if is in the list, but that errors during instantiation of other templates (such as iterator_value) during argument deduction are not.

    Team LiB
    Previous Section Next Section