C++ SFINAE enable_if_t in member function, how to disambiguate?
Asked Answered
D

3

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.

Diogenes answered 29/8, 2018 at 18:19 Comment(2)
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 statementProtostele
Related: https://mcmap.net/q/1630995/-sfinae-constructors-duplicate/1896169Melba
P
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

Protostele answered 29/8, 2018 at 18:22 Comment(8)
Ah, and I would need to add the same typename std::enable_if<...> in the definition as well, got it.Diogenes
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)Protostele
My approach is typename<class S, std::enable_if_t<condition>* = nullptr>Devito
@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.Protostele
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, thanksDiogenes
@OneRaynyDay: Updated with C++17Protostele
@milleniumbug: Here's the reference: cplusplus.github.io/EWG/ewg-active.html#175Protostele
@Protostele Wow, C++, never failing to surprise me. I hope the issue will be resolved to make it legal.Devito
F
7

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.

Frisky answered 29/8, 2018 at 18:33 Comment(1)
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 <bool B, typename T = void> using enable_if_t = typename std::enable_if<B, T>::type; to make it C++11?Convulsant
C
5

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)
{
    ... 
}
Convulsant answered 29/8, 2018 at 18:30 Comment(5)
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 yoursDiogenes
@OneRaynyDay, Andy has updated his answer, so I removed constexpr solution and added another one for C++11.Convulsant
@Evgeny: Love tagged dispatch +1. You can just pass std::is_integral<S>{} directly to bar without needing to wrap it with an integral_constant DemoProtostele
@AndyG, very good remark, thanks. std::is_integral inherits from std::integral_constant. Corrected the answer.Convulsant
@Evgeny: Precisely. Possible object slicing? Definitely! Do we care? Heck no!Protostele

© 2022 - 2024 — McMap. All rights reserved.