24

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.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 2
    `std::enable_if_t` is c++14 btw. – Julien Lopez Mar 25 '16 at 14:16
  • 1
    It has to be a good day when you're overwhelmed with correct solutions to your problem :-) – Richard Hodges Mar 25 '16 at 14:23
  • good explanation here: http://stackoverflow.com/questions/31500426/why-does-enable-if-t-in-template-arguments-complains-about-redefinitions – johngull Mar 25 '16 at 14:25
  • 1
    @RichardHodges: With experience you manage to increase your chances of asking a well-received question. Unfortunately, it is usually the inexperienced who need this more. Alas. – einpoklum Mar 25 '16 at 14:43
  • I don't have time to write a full fledged answer for this idea, but one idea that departs from all the answers so far is to use a meta programming library like boost::mpl or loki and build a compile time associative container that associates type traits to functors and to look up the correct function at compile time using that data structure. You can easily extend this to work for huge numbers of possible combinations of traits and it makes the association between traits and functionality clear. – bfair Mar 25 '16 at 15:04
  • @BrianFairservice: Would that be all that different from sergej's [answer](http://stackoverflow.com/a/36221751/1593077)? – einpoklum Mar 25 '16 at 15:08
  • @einpoklum Eh, I think its mainly a stylistic difference. Building an associative data structure in the way I described I think makes the association between certain traits and the functionality to use for them more explicit than using c++'s function overloading mechanism to describe that association. I think that clarity could benefit you if there are huge amounts of combinations of traits you are working with. But then you are also introducing a new dependency to your project, and you will also see much larger compile times. – bfair Mar 25 '16 at 15:25

5 Answers5

14

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 );
Barry
  • 286,269
  • 29
  • 621
  • 977
  • I don't understand your need for `tag`. It seems to be the same with the new parameter not doing anything. And the first example was overloading `foo_impl` too (not specializing). P.s. you missed a comma in the last fallback. – JDługosz Mar 25 '16 at 16:04
  • @JDługosz Did you see what I did with `int`? The `tag` allows me to add an overload like that. – Barry Mar 25 '16 at 16:06
  • I see what you are doing, after reading the linked article. The choice struct provides ordered overloads, and the other paramers (if yiu had any) would be based on T and match exactly. So if the other params are all the same across the board, the choice provides a ranking of all those enabled. Cool! – JDługosz Mar 25 '16 at 16:09
  • Ah, the `tag` makes it easy to add exact single-type matches without using the enable-if. In that case the function won't be a template anyway, so it's simpler (at the cost of adding the param to all others). Assuming the c-v, ref, etc. conversions don't mess it up. – JDługosz Mar 25 '16 at 16:13
  • Looks like this machinery belongs in some template/type traits utilities library. – einpoklum Mar 26 '16 at 00:07
10

One more option using tag dispatch (C++11):

#include <iostream>

void foo_impl(std::false_type) {
    std::cout << "Any T." << std::endl;
}

void foo_impl(std::true_type) {
    std::cout << "T is integral." << std::endl;
}

template <typename T>
void foo() {
    foo_impl(std::is_integral<typename std::remove_reference<T>::type>());
  //foo_impl(std::is_integral<typename std::remove_reference_t<T>>()); // C++14
}

int main() { 
    foo<short>();  // --> T is integral.
    foo<short&>(); // --> T is integral.
    foo<float>();  // --> Any T.
}

Borrowed from Scott Meyers Effective Modern C++ item 27.

sergej
  • 17,147
  • 6
  • 52
  • 89
6

One way:

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

template <typename T, typename std::enable_if_t<not std::is_integral<T>::value>* = nullptr>
void foo() { std::cout << "Any T." << std::endl; }

Another way is to defer to a template function object:

template<class T, typename = void>
struct foo_impl
{
    void operator()() const {
        std::cout << "Any T." << std::endl;
    }
};

template<class T>
struct foo_impl<T, std::enable_if_t<std::is_integral<T>::value>>
{
    void operator()() const {
        std::cout << "T is integral." << std::endl;
    }
};

template<class T>
void foo() {
    return foo_impl<T>()();
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Is the class wrapper really necessary to avoid "disjoint conditions" for the general and integral-specific versions? – einpoklum Mar 25 '16 at 14:46
  • Deferring to a class allows me to bring partial specialisation to the party. I find it a lot more flexible. – Richard Hodges Mar 25 '16 at 14:49
  • 2
    But answer your question, directly, it's either defer to a class or defer to tag dispatching if you want to avoid writing in terms of disjoint conditions. – Richard Hodges Mar 25 '16 at 14:51
4

AFAIK, sfinae is applicable to function params so try to add parameter of dependent type with default value

template <typename T>
void foo(typename std::enable_if_t<std::is_integral<T>::value>* = 0) 
{ std::cout << "T is integral." << std::endl; }

template <typename T>
void foo(typename std::enable_if_t<!std::is_integral<T>::value>* = 0) 
{ std::cout << "Any T." << std::endl; }
user2807083
  • 2,962
  • 4
  • 29
  • 37
  • This works, but then you have code which applies to any T conditioned on T not being integral, which is somewhat confusing. Also, doesn't scale well, since tomorrow someone will want another specialization for non-integral types whose size is at least 7; then another specialization; etc etc, and every time you'd need to qualify the condition on the general version. – einpoklum Mar 25 '16 at 14:38
  • 2
    Well, than you have @Barry answer, which is scalable as I understand. – user2807083 Mar 25 '16 at 15:37
4

One way to do this is:

template <typename T>
std::enable_if_t<std::is_integral<T>::value, void> foo () {
    std::cout << "integral version" << std::endl;
}

template <typename T>
std::enable_if_t<!std::is_integral<T>::value, void> foo () {
    std::cout << "general version" << std::endl;
}

with usage:

foo<int> ();
foo<double> ();
struct X {};
foo<X> ();

output is:

integral version
general version
general version
Artur Pyszczuk
  • 1,920
  • 1
  • 16
  • 23
  • Same comment as for @user2807083: This works, but then you have code which applies to any T conditioned on T not being integral, which is somewhat confusing. Also, doesn't scale well, since tomorrow someone will want another specialization for non-integral types whose size is at least 7; then another specialization; etc etc, and every time you'd need to qualify the condition on the general version. – einpoklum Mar 25 '16 at 14:39