Thursday 1 May 2014

Solving SFINAE issues when you have overlapping conditions

Sometimes we have function templates which we want to use SFINAE on, but some of them have overlapping conditions, creating ambiguity

template<unsigned N, enable_if_t<is_multiple_of<N, 3>>...>
void print_fizzbuzz(){ std::cout << "fizz\n"; }

template<unsigned N, enable_if_t<is_multiple_of<N, 5>>...>
void print_fizzbuzz(){ std::cout << "buzz\n"; }

template<unsigned N, enable_if_t<is_multiple_of<N, 15>>...> // this is ambiguous
void print_fizzbuzz(){ std::cout << "fizzbuzz\n"; }

By using derived-to-base conversions we can create a total ordering for selecting SFINAE overloads.

That is, we resolve ambiguity by using the following inheritance hierarchy:

template<unsigned I> struct choice : choice<I+1>{};

choice<0> has a higher ordering than choice<1>, and we can therefore use choice<0> as a function parameter to make is_multiple_of<N, 15> a better overload, thereby resolving the ambiguity.

The complete fizzbuzz example:

#include <type_traits>
#include <iostream>

template<class C, class T = int>
using enable_if_t = typename std::enable_if<C::value, T>::type;

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

//-------------------------------

template<unsigned I> struct choice : choice<I+1>{};
template<> struct choice<10>{}; // suitably high terminating condition

struct otherwise{ otherwise(...){} };

struct select_overload : choice<0>{};

//-------------------------------

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

template<unsigned N, enable_if_t< is_multiple_of<N, 3> >...>
void print_fizzbuzz(choice<1>) { std::cout << "fizz\n"; }

template<unsigned N, enable_if_t< is_multiple_of<N, 5> >...>
void print_fizzbuzz(choice<2>) { std::cout << "buzz\n"; }

template<unsigned N>
void print_fizzbuzz(otherwise){ std::cout << N << "\n"; }

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

template<>
void do_fizzbuzz<50>()
{
    print_fizzbuzz<50>(select_overload{});
}

//-------------------------------

int main()
{
    do_fizzbuzz();
}

This excellent technique by Xeo, as described here

No comments:

Post a Comment