Ultimate Overloads

Generic algorithms in C++ operate by substituting specific types into templates that use features of the underlying types. Optimized implementations of algorithms can be selected when the type parameters satisfy certain constraints. A standard technique for this is overload selection via tag dispatching, but maybe you want a more abstract solution.

As usual, code examples are available in a github gist.

As shown previously, std::enable_if can be used to select an override if types are assignable. The condition can be arbitrarily complex; taking advantage of decltype and std::declval you could even check whether a particular member function can be called and will return a particular type:

namespace details {
template <typename T,
          typename = std::enable_if<std::is_same<void, decltype(std::declval<T>().clear())>::value>>
void clear_ (T& value, int)
{
  value.clear();
}

template <typename T>
void clear_ (T& value, long)
{
  value = std::move(T{});
}
} // ns details

template <typename T>
void clear (T& value)
{
  return details::clear_(value, 0);
}

The problem is that you need something to distinguish and prioritize the different overloads, so when multiple candidates are possible the best one is selected. In this case we used int and long. The blog post Remastered enable_if covers the options well, but the final word is in a followup post Beating overload resolution into submission which presents a solution with unbounded extensibility. It’s the one at the bottom of that post, if you don’t want to read the whole thing (though you should).

All the heavy lifting was done in those posts. I want to add a little value because some things about the solution leave me discomforted:

  1. There’s a magic number 10 used to ground the template recursion, based on an assumption that no more than 10 overloads are necessary;
  2. There’s a distinct type that’s used for the “none-of-the-above” situation, which in my view is non-orthogonal;
  3. The example doesn’t work with clang: it compiles without warning, but prints “fizzbuzz” for every N.

Let’s deal with the last one first. Each overload is of the form:

template<unsigned N, EnableIf<is_multiple_of<N, 15>>...>
void print_fizzbuzz(choice<0>){ std::cout << "fizzbuzz\n"; }

but it’s clear the underlying std::enable_if hidden inside the EnableIf isn’t doing its job.

I reduced this to LLVM bug 18677, which was promptly marked as a duplicate of LLVM bug 11723, showing that the bug has been present for about two years, so it’s clearly not a priority. In fact, it’s mentioned in the fine print of Remastered. The upshot is: using a variadic parameter pack to make signatures distinct without requiring an instance is a neat trick, but if you want to be portable to clang you can’t use it.

Fortunately, the solution in Beating shows us how to get an unbounded number of unique types that form an overload hierarchy without caring whether the template parameter is unique, so all we need do is change the canonical form back to the more traditional default template parameter:

template<unsigned N, typename = typename std::enable_if<is_multiple_of<N, 15>::value>::type>
void print_fizzbuzz(choice<0>){ std::cout << "fizzbuzz\n"; }

This has the added benefit (IMO) of removing the custom template alias and directly using only constructs for which the meaning is well-defined and in the standard namespace.

The other two concerns are dealt with simply by inverting the priority indicated by the unsigned template parameter: let 0 be the lowest priority, which becomes what you use for the default case. To start the recursion, just keep track of the maximum overload count required for the particular function you need to support. Here’s the whole solution:

/* From: https://ideone.com/IB6tIR
 * Documented at: http://flamingdangerzone.com/cxx11/2013/03/11/overload-ranking.html
 * By: http://stackoverflow.com/users/500104/xeo
 * Modified: pabigot 20140201
 */

#include <type_traits>
#include <iostream>

template<int N, int M>
struct is_multiple_of : std::integral_constant<bool, N % M == 0>{};

template<unsigned I> struct overload_weight : overload_weight<I-1>{};
/* Lowest priority, use for default selection */
template<> struct overload_weight<0>{};

/* Helper constant, kept up to date as overload list is changed */
#define FIZZBUZZ_OVERLOAD_MAX_WEIGHT 10

template<unsigned N, typename = typename std::enable_if<is_multiple_of<N, 15>::value>::type>
void print_fizzbuzz(overload_weight<10>){ std::cout << "fizzbuzz\n"; }

template<unsigned N, typename = typename std::enable_if<is_multiple_of<N, 21>::value>::type>
void print_fizzbuzz(overload_weight<9>){ std::cout << "fizzbeep\n"; }

template<unsigned N, typename = typename std::enable_if<is_multiple_of<N, 33>::value>::type>
void print_fizzbuzz(overload_weight<8>){ std::cout << "fizznarf\n"; }

template<unsigned N, typename = typename std::enable_if<is_multiple_of<N, 35>::value>::type>
void print_fizzbuzz(overload_weight<7>){ std::cout << "buzzbeep\n"; }

template<unsigned N, typename = typename std::enable_if<is_multiple_of<N, 55>::value>::type>
void print_fizzbuzz(overload_weight<6>){ std::cout << "buzznarf\n"; }

template<unsigned N, typename = typename std::enable_if<is_multiple_of<N, 77>::value>::type>
void print_fizzbuzz(overload_weight<5>){ std::cout << "beepnarf\n"; }

template<unsigned N, typename = typename std::enable_if<is_multiple_of<N, 3>::value>::type>
void print_fizzbuzz(overload_weight<4>){ std::cout << "fizz\n"; }

template<unsigned N, typename = typename std::enable_if<is_multiple_of<N, 5>::value>::type>
void print_fizzbuzz(overload_weight<3>){ std::cout << "buzz\n"; }

template<unsigned N, typename = typename std::enable_if<is_multiple_of<N, 7>::value>::type>
void print_fizzbuzz(overload_weight<2>){ std::cout << "beep\n"; }

template<unsigned N, typename = typename std::enable_if<is_multiple_of<N, 11>::value>::type>
void print_fizzbuzz(overload_weight<1>){ std::cout << "narf\n"; }

/* No conditional on default case */
template<unsigned N>
void print_fizzbuzz(overload_weight<0>){ std::cout << N << "\n"; }

template<unsigned N = 1>
void do_fizzbuzz(){
    print_fizzbuzz<N>(overload_weight<FIZZBUZZ_OVERLOAD_MAX_WEIGHT>{});
    do_fizzbuzz<N+1>();
}

template<>
void do_fizzbuzz<100>(){
    print_fizzbuzz<100>(overload_weight<FIZZBUZZ_OVERLOAD_MAX_WEIGHT>{});
}

int main(){
  do_fizzbuzz();
}

A trivial change, but this allows us to put the overload_weight template into a library and use it for multiple functions without having to change it if somebody adds more alternatives than were anticipated.

Leave a Reply

Your email address will not be published. Required fields are marked *