Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the right way to fix this template resolution ambiguity?

Tags:

Suppose I've written:

template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>> void foo() { std::cout << "T is integral." << std::endl; }  template <typename T> void foo() { std::cout << "Any T." << std::endl; }  int main() { foo<short>(); } 

When I compile this, I get an error about the ambiguity of the call (and no error if, say, I replace short with float). How should I fix this code so that I get the upper version for integral types and lower version otherwise?

Bonus points if your suggestion scales to the case of multiple specialized versions of foo() in addition to the general one.

like image 746
einpoklum Avatar asked Mar 25 '16 14:03

einpoklum


1 Answers

I like Xeo's approach for this problem. Let's do some tag dispatch with a fallback. Create a chooser struct that inherits from itself all the way down:

template <int I> struct choice : choice<I + 1> { };  template <> struct choice<10> { }; // just stop somewhere 

So choice<x> is convertible to choice<y> for x < y, which means that choice<0> is the best choice. Now, you need a last case:

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

With that machinery, we can forward our main function template with an extra argument:

template <class T> void foo() { foo_impl<T>(choice<0>{}); } 

And then make your top choice integral and your worst-case option... anything:

template <class T, class = std::enable_if_t<std::is_integral<T>::value>> void foo_impl(choice<0> ) {     std::cout << "T is integral." << std::endl; }  template <typename T> void foo_impl(otherwise ) {     std::cout << "Any T." << std::endl; } 

This makes it very easy to add more options in the middle. Just add an overload for choice<1> or choice<2> or whatever. No need for disjoint conditions either. The preferential overload resolution for choice<x> takes care of that.

Even better if you additionally pass in the T as an argument, because overloading is way better than specializing:

template <class T> struct tag {}; template <class T> void foo() { foo_impl(tag<T>{}, choice<0>{}); } 

And then you can go wild:

// special 1st choice for just int void foo_impl(tag<int>, choice<0> );  // backup 1st choice for any integral template <class T, class = std::enable_if_t<std::is_integral<T>::value>> void foo_impl(tag<T>, choice<0> );  // 2nd option for floats template <class T, class = std::enable_if_t<std::is_floating_point<T>::value>> void foo_impl(tag<T>, choice<1> );  // 3rd option for some other type trait template <class T, class = std::enable_if_t<whatever<T>::value>> void foo_impl(tag<T>, choice<2> );  // fallback  template <class T> void foo_impl(tag<T>, otherwise ); 
like image 150
Barry Avatar answered Oct 10 '22 05:10

Barry