6

Suppose we have some SFINAE member function:

class foo{
    template <class S, class = std::enable_if_t<std::is_integral<S>::value, S>
    void bar(S&& s);
    template <class S, class = std::enable_if_t<!std::is_integral<S>::value, S>
    void bar(S&& s);
}

If we declared it as above, then how can we define them? Both of their function signatures would look like:

template <class S, class>
inline void foo::bar(S&& s){ ... do something ... }

I have seen examples where one returns an std::enable_if_t<...> like:

template <class S, class>
auto bar(S&& s) -> std::enable_if_t<!std::is_integral<S>::value, S>(...){
    ... do something ...
}

To disambiguate based off of the return type. But I don't want to return anything.

Barry
  • 286,269
  • 29
  • 621
  • 977
OneRaynyDay
  • 3,658
  • 2
  • 23
  • 56
  • 1
    It doesn't look like you're actually using C++11. If you're actually using C++17 we can make things easier with a constexpr if statement – AndyG Aug 29 '18 at 18:27
  • 1
    Related: https://stackoverflow.com/q/51292574/1896169 – Justin Aug 29 '18 at 18:29

3 Answers3

11

since default arguments are not part of a function signature, make them not default

class foo{
    template <class S, typename std::enable_if<std::is_integral<S>::value, int>::type = 0>
    void bar(S&& s);
    template <class S, typename std::enable_if<!std::is_integral<S>::value, int>::type = 0>
    void bar(S&& s);
};

Live Demo


EDIT: by popular demand, Here's the same code in C++17:

class foo{
public:
    template <class S>
    void bar(S&& s)
    {
        if constexpr(std::is_integral_v<S>)
            std::cout << "is integral\n";
        else
            std::cout << "NOT integral\n";
    }
};

constexpr if statements are special to the compiler because the branch is chosen at compile time, and the non-taken branch isn't even instantiated

C++17 Demo

Barry
  • 286,269
  • 29
  • 621
  • 977
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • Ah, and I would need to add the same `typename std::enable_if<...>` in the definition as well, got it. – OneRaynyDay Aug 29 '18 at 18:24
  • Yeah, the gist is not to use `class = ` syntax, but rather use an explicit type. Preferably an integral one so that you can default its value (like we did with `int` and `0`) – AndyG Aug 29 '18 at 18:25
  • My approach is `typename* = nullptr>` – milleniumbug Aug 29 '18 at 18:27
  • @milleniumbug: Whether that is defined behavior or not is up for debate (`void*` is not a pointer to a class type) Let me find the issue page for that. – AndyG Aug 29 '18 at 18:28
  • I see - so why do we need to set = 0 or = nullptr? I've seen usages where you didn't need to do that. Also, could you add the constexpr answer? I'd love to learn about it. I am using c++11 in this case, but still. I have since upvoted and will accept your answer, thanks – OneRaynyDay Aug 29 '18 at 18:28
  • @OneRaynyDay: Updated with C++17 – AndyG Aug 29 '18 at 18:32
  • @milleniumbug: Here's the reference: https://cplusplus.github.io/EWG/ewg-active.html#175 – AndyG Aug 29 '18 at 18:34
  • @AndyG Wow, C++, never failing to surprise me. I hope the issue will be resolved to make it legal. – milleniumbug Aug 29 '18 at 19:17
6

With C++11 compiler another option is to use tag dispatching.

template <class S>
void bar(S&& s)
{
    bar(std::forward<S>(s), std::is_integral<S>{});
}

template <class S>
void bar(S&& s, std::true_type)
{
    ... 
}

template <class S>
void bar(S&& s, std::false_type)
{
    ... 
}
Evg
  • 25,259
  • 5
  • 41
  • 83
  • Ah, thank you, I haven't yet familiarized myself with constexpr. Since I am using c++11 I'll have to accept Andy's answer, but I have upvoted yours – OneRaynyDay Aug 29 '18 at 18:31
  • @OneRaynyDay, Andy has updated his answer, so I removed `constexpr` solution and added another one for C++11. – Evg Aug 29 '18 at 18:43
  • @Evgeny: Love tagged dispatch +1. You can just pass std::is_integral{} directly to `bar` without needing to wrap it with an `integral_constant` [Demo](https://wandbox.org/permlink/vwAZpZFsb6rtff10) – AndyG Aug 29 '18 at 19:14
  • @AndyG, very good remark, thanks. `std::is_integral` inherits from `std::integral_constant`. Corrected the answer. – Evg Aug 29 '18 at 19:18
  • 1
    @Evgeny: Precisely. Possible object slicing? Definitely! Do we care? Heck no! – AndyG Aug 29 '18 at 19:20
6

You can still do this in the return type just fine. Just keep the default of enable_if (which is void). Even if you're just on C++11, just add this alias:

template <bool B, typename T=void>
using enable_if_t = typename std::enable_if<B, T>::type;

And then you can do:

template <class S>
enable_if_t<std::is_integral<S>::value>
bar(S);

template <class S>
enable_if_t<!std::is_integral<S>::value>
bar(S);

Or:

template <class S>
auto bar(S) -> enable_if_t<std::is_integral<S>::value>

template <class S>
auto bar(S) -> enable_if_t<!std::is_integral<S>::value>

Either way, you have two properly disambiguated functions that return void.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • What is the point of redefining `std::enable_if_t` which already has `T = void` as a default argument? Your code compiles with `std::enable_if_t` just fine (using C++14). Maybe you wanted to write `template using enable_if_t = typename std::enable_if::type;` to make it C++11? – Evg Aug 29 '18 at 18:59