Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 987
OneRaynyDay Avatar asked Aug 29 '18 18:08

OneRaynyDay


3 Answers

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

like image 111
AndyG Avatar answered Oct 15 '22 00:10

AndyG


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.

like image 6
Barry Avatar answered Oct 15 '22 02:10

Barry


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)
{
    ... 
}
like image 6
Evg Avatar answered Oct 15 '22 02:10

Evg