user13017513
user13017513

Reputation:

Why this SFINAE fails?

struct BLA
{

};


template<typename T>
class DUMMY
{
public:
    DUMMY() = default;

    template<typename U = T, typename = void>
    void someFunction()
    {
        std::cout << std::is_same<U, BLA>::value << "\n";
        std::cout << "someFunction() - DEFAULT\n";
    }

    template<typename U = T, typename std::enable_if<std::is_same<U, BLA>::value, void>::type>
    void someFunction()
    {
        std::cout << "someFunction()\n";
    }
};


int main()
{
    DUMMY<BLA> dummy;

    dummy.someFunction();
}

Why is this SFINAE code calling someFunction() that displays "someFunction() - DEFAULT"? It should call the other one. It is clear that std::is_same::value is true.

Upvotes: 1

Views: 201

Answers (2)

calynr
calynr

Reputation: 1282

I am writing as new answer because it doesn't fit to comment. For addition to @Jarod42.

It seems you assumed that

template<typename U = T, typename std::enable_if<std::is_same<U, BLA>::value, void>::type>

substitutes to

template<typename U = T, typename = void>

but it doesn't. It substitutes to

template<typename U = T, void>.

So, you should declare it as

template<typename U = T, typename = typename std::enable_if<std::is_same<U, BLA>::value, void>::type>.

Because the typename you used is for specifying the type is dependant, not for the template parameter declaration. But either won't work as you expected anyway. One silently removes the ill-formed, other one results in multiple declaration of the same function.

After your comment, i try to explain more detailed.

template<typename U = T, typename std::enable_if<std::is_same<U, BLA>::value, void>::type>
void someFunction()
{
    std::cout << "someFunction()\n";
}

If T == BLA, U becomes BLA and it makes std::is_same< U , BLA>::value true. So the result looks like this

template<typename U = BLA, void>

If T == NotBlaType, U becomes NotBlaType and it makes std::is_same<U,BLA>::value false. The result is substition failure, std::enable_if<false,void> has no type.

But in both case, the function is not declared. Because void cannot be allowed as non-type template parameter.

But if we change void to int, then it is allowed. That's why @Jarod42 suggests int.

template<void> is not legal.

But template<int = 2> is legal.

After making the declaration is valid, you should toggle declarations of functions conditionally (because both functions have same signature so causes to multiple declaration). That's why both functions in the @Jarod42's answer have std::enable_if which evalutes negate of each other.

Upvotes: 0

Jarod42
Jarod42

Reputation: 218278

template<typename U = T, typename std::enable_if<std::is_same<U, BLA>::value, void>::type> would result (with correct substitution) in template<typename U = T, void> which is invalid.

You might change to

template<typename U = T, typename std::enable_if<std::is_same<U, BLA>::value, int>::type = 0>

but then, both function would be viable, and so ambiguous.

So, you might finally do

    template<typename U = T, typename std::enable_if<!std::is_same<U, BLA>::value, int>::type = 0>
    void someFunction()
    {
        std::cout << std::is_same<U, BLA>::value << "\n";
        std::cout << "someFunction() - DEFAULT\n";
    }

    template<typename U = T, typename std::enable_if<std::is_same<U, BLA>::value, int>::type = 0>
    void someFunction()
    {
        std::cout << "someFunction()\n";
    }

In C++17, simpler to do

    void someFunction()
    {
        if constexpr (std::is_same<U, BLA>::value) {
            std::cout << "someFunction()\n";
        } else {
            std::cout << std::is_same<U, BLA>::value << "\n";
            std::cout << "someFunction() - DEFAULT\n";
        }
    }

Upvotes: 2

Related Questions