Search code examples
templatesc++11enable-if

Selecting a functions implementation based on iterator::value_type


I'm looking for a reasonable way to select a sort algorithm based on the value type of the container.

In its current form I can deduce the proper sort(a, b) for integer/non-integer data.

#include <cstdlib>
#include <type_traits>
#include <algorithm>
#include <vector>
#include <iostream>

namespace sort_selector{
    template<typename T>
    void _radix_sort(T begin, T end){
        // radix implementation
    }

    template<typename T>
    typename std::enable_if<
                  std::is_integral<typename T::value_type>::value>::type
    sort(T begin, T end){
        std::cout << "Doing radix" << std::endl;
        sort_selector::_radix_sort(begin, end);
    }

    template<typename T>
    typename std::enable_if<
                  !std::is_integral<typename T::value_type>::value>::type
    sort(T begin, T end){
        std::cout << "Doing sort" << std::endl;
        std::sort(begin, end);
    }
}
int main(int argc, char** argv) {
    std::vector<double> for_stdsort = {1, 4, 6, 2};
    std::vector<int32_t> for_radixsort = {1, 4, 6, 2};
    //std::array<int32_t, 4> array_for_radixsort = {1, 4, 6, 2};

    sort_selector::sort(std::begin(for_stdsort), std::end(for_stdsort));
    sort_selector::sort(std::begin(for_radixsort), std::end(for_radixsort));
    //sort_selector::sort(std::begin(array_for_radixsort), 
    //                     std::end(array_for_radixsort));

    return 0;
}
  1. I would like to be able to use array-like iterators. (they have no ::value_type).
  2. I would like to be able to distinguish between say, int32_t and int64_t.

I'm at a complete loss as how to achieve this in any reasonably simple way. I.e. not specializing for every instance.


Solution

  • Use std::iterator_traits<T>::value_type to retrieve the value type of an iterator; it works for pointers as well as class-type iterators.

    For dispatching, I would use template specialization to select the proper implementation (Live Demo):

    namespace sort_selector {
    // Default to using std::sort
    template <typename T, typename = void>
    struct dispatcher {
      template <typename Iterator>
      static void sort(Iterator begin, Iterator end) {
        std::cout << "Doing std::sort\n";
        std::sort(begin, end);
      }
    };
    
    // Use custom radix sort implementation for integral types
    template <typename T>
    struct dispatcher<T, typename std::enable_if<std::is_integral<T>::value>::type> {
      template <typename Iterator>
      static void sort(Iterator, Iterator) {
        std::cout << "Doing radix\n";
        // radix implementation
      }
    };
    
    // Use some other specific stuff for int32_t
    template <>
    struct dispatcher<int32_t, void> {
      template <typename Iterator>
      static void sort(Iterator, Iterator) {
        std::cout << "Specific overload for int32_t\n";
        // Do something
      }
    };
    
    // Dispatch appropriately
    template <typename Iterator>
    inline void sort(Iterator begin, Iterator end) {
      dispatcher<typename std::iterator_traits<Iterator>::value_type>::sort(begin, end);
    }
    } // namespace sort_selector
    

    You should probably constrain sort_selector::sort to require random access iterators so your error messages are more digestible when someone inevitably tries to pass an improper iterator type:

    namespace sort_selector {
    // Dispatch appropriately
    template <typename Iterator>
    inline void sort(Iterator begin, Iterator end) {
      using traits = std::iterator_traits<Iterator>;
      static_assert(
        std::is_base_of<
          std::random_access_iterator_tag,
          typename traits::iterator_category
        >::value, "sorting requires random access iterators");
      dispatcher<typename traits::value_type>::sort(begin, end);
    }
    } // namespace sort_selector