Section 9.2.  Implementation Selection
Team LiB
Previous Section Next Section

9.2. Implementation Selection

In this section we'll discuss a few different ways to choose different runtime behaviors or interfaces based on the result of some compile time computation.

9.2.1. if Statements

The most straightforward way to control the implementation of a runtime function template is to test a static condition in an if statement, as follows:



    template <class T>


    void f(T x)


    {


        if (boost::is_class<T>::value)


        {


            ...implementation 1...


        }


        else


        {


            ...implementation 2...


        }


    }



Since the condition can be completely determined at compile time, many compilers will optimize away the is_class test, and will only generate code for the branch of the if that is selected.

This approach is clear and simple, with little or no conceptual overheadwhen it works. Unfortunately, the technique isn't universally applicable. For example, consider what happens when the function above is implemented this way:



    template <class T>


    void f(T x)


    {


        if (boost::is_class<T>::value)


        {


            std::cout << x::value;   // handle integral wrappers


        }


        else


        {


            std::cout << x;          // handle non-wrappers


        }


    }



The intention here was for f to be able to print the value of an integral type (e.g., int) or of an integral constant wrapper (e.g., long_<5>). If we invoke f(42), though, we'll get a compilation error. The problem is that the entire function body needs to typecheck, including both branches of the if statement, and we can't access the nonexistent ::value member of an int.

9.2.2. Class Template Specialization

We can address the previous problem by moving each branch of our if statement into a distinct function: a static member function of a class template. By specializing the class template, we can decide which function implementation gets used:



    template <bool> // handle integral constant wrappers


    struct f_impl


    {


        template <class T>


        static void print(T x) { std::cout << x::value; }


    };





    template <>     // specialization for non-wrappers


    struct f_impl<false>


    {


        template <class T>


        static void print(T x) { std::cout << x; }


    };





    template <class T>


    void f(T x)


    {


        f_impl<boost::is_class<T>::value>::print(x);


    };



This approach is similar to the one we used to implement iter_swap in Chapter 2, and the version using mpl::if_, introduced in Chapter 4, is a variation on the same theme. We'll see the same basic idea evolve further when we cover structure selection later in this chapter.

9.2.3. Tag Dispatching

We already got a taste of the tag dispatching concept from our work on the tiny sequence in Chapter 5, but the fundamental idea was actually borrowed from generic programming in the runtime domain. Runtime tag dispatching uses function overloading to generate executable code based on properties of a type.

A good example can be found in the advance algorithm of most C++ standard library implementations. Though conceptually simpleadvance moves an iterator i by n positionsactually writing the algorithm is fairly complex. Depending on the traversal capabilities of the iterator, entirely distinct implementation strategies are required. For example, if i supports random access, then advance can be implemented with i += n and is very efficient: constant time. Other iterators must be advanced in steps, making the operation linear in n. If i is bidirectional, then it makes sense for n to be negative, so we must decide at runtime whether to increment or decrement the iterator. Any function that decrements an iterator, however, would fail to compile when passed an iterator supporting only forward traversal. So, advance requires at least three different implementations.

To select among them, we must use the concept information contained in the following category tag types:



    namespace std


    {


      struct input_iterator_tag { };


      struct forward_iterator_tag


        : input_iterator_tag { };





      struct bidirectional_iterator_tag


        : forward_iterator_tag { };





      struct random_access_iterator_tag


        : bidirectional_iterator_tag { };


    }



A tag is simply an empty class whose only purpose is to convey some information at compile time, in this case the iterator concept modeled by a given iterator type. Every iterator type I has an associated category tag, which can be accessed as



    std::iterator_traits<I>::iterator_category



Note that in this case the tags belong to an inheritance hierarchy that mirrors the refinement hierarchy of the concepts they represent. For example, every bidirectional iterator is also a forward iterator, so bidirectional_iterator_tag is derived from forward_iterator_tag.

Once again, we'll separate the three implementations into distinct function bodies, but this time we'll use overloading to select the right one by passing an instance of the iterator's empty tag type as an argument.



    namespace std


    {


      template <class InputIterator, class Distance>


      void __advance_impl(


          InputIterator& i


        , Distance n


        , input_iterator_tag)


      {


          while (n--) ++i;


      }





      template <class BidirectionalIterator, class Distance>


      void __advance_impl(


          BidirectionalIterator& i


        , Distance n


        , bidirectional_iterator_tag)


      {


          if (n >= 0)


            while (n--) ++i;


          else


            while (n++) --i;


      }





      template <class RandomAccessIterator, class Distance>


      void __advance_impl(


          RandomAccessIterator& i


        , Distance n


        , random_access_iterator_tag)


      {


          i += n;


      }





      template <class InputIterator, class Distance>


      void advance(InputIterator& i, Distance n)


      {


          typedef typename


            iterator_traits<InputIterator>::iterator_category


          category;


 


          __advance_impl(i, n, category());


      }


    }



The outer advance function calls the __advance_impl overload that best matches the tag; the other overloads, which may use operations not implemented by a given iterator, are never instantiated. Here the inheritance hierarchy used for iterator tags works to our advantage: There is no __advance_impl specifically written for iterators whose category is forward_iterator_tag, but since forward_iterator_tag is derived from input_iterator_tag, the compiler selects the input_iterator_tag version for input iterators and forward iterators. That would not have been possible had we used specialization on tag types to select implementations.

Note that mpl::true_ and mpl::false_ make fine dispatching tags. In the example below, desperate_cast<T>(x) is equivalent to static_cast<T>(x) unless x happens to be (a pointer to) an object of polymorphic class type, in which case desperate_cast<T>(x) is equivalent to dynamic_cast<T>(x).



    // implementation for polymorphic types


    template <class T, class U>


    T desperate_cast_impl2(U& x,  mpl::true_)


    {


        return dynamic_cast<T>(x); // legal iff U is polymorphic


    }





    // implementation for non-polymorphic types


    template <class T, class U>


    T desperate_cast_impl2(U& x,  mpl::false_)


    {


        return static_cast<T>(x);


    }





    // dispatcher


    template <class T, class U>


    T desperate_cast_impl(U& x)


    {


        return desperate_cast_impl2<T>(


            x


          , boost::is_polymorphic<


                typename boost::remove_pointer<U>::type


            >()


        );


    }





    // public interface


    template <class T, class U>


    T desperate_cast(U const& x) { return desperate_cast_impl<T>(x); }





    template <class T, class U>


    T desperate_cast(U& x) { return desperate_cast_impl<T>(x); }



Because of the way the integral-valued type traits are derived from their result types, we only need to create an object of the whole metafunction specialization boost::is_polymorphic<...>() to produce a tag that will match mpl::true_ or mpl::false_.

    Team LiB
    Previous Section Next Section