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.
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 );
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With