1
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.

  • what kind of compiler? on last gcc work fine https://gcc.godbolt.org/z/uGHn5t – Arenoros Mar 06 '20 at 08:41
  • VS2019, cl.exe, version 19.24.28315 –  Mar 06 '20 at 08:44
  • @ProXicT, even if I add the same enable_if to the first method, only with `!` in front of is_same, everything is the same. Do you have any suggestions on how to solve this? –  Mar 06 '20 at 08:46
  • Duplicate of https://stackoverflow.com/questions/50440352/sfinae-delete-a-function-with-the-same-prototype ? – Antoine Morrier Mar 06 '20 at 08:57

2 Answers2

2

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";
        }
    }
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Could you explain that utterly weird 'int'? –  Mar 06 '20 at 08:53
  • Check https://stackoverflow.com/a/50440663/4950448 this answer :) – Antoine Morrier Mar 06 '20 at 08:58
  • @Antoine, there is no any difference. void is replaced with int and then is works out of nowhere. Why is int so important in SFINAE? What kind of magic is behind that? –  Mar 06 '20 at 09:00
  • That "results" in `template`, you might use other type/value. other more fragile way (can be hijacked, doesn't support overload) is `typename = std::enable_if::value>::type` (which results is `typename = void`). – Jarod42 Mar 06 '20 at 09:00
  • Just read the link I sent you, you'll understand. Prototypes are the same. In the second case prototypes are differents. Function with same prototypes are not allowed in C++. – Antoine Morrier Mar 06 '20 at 10:29
0

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.

calynr
  • 1,264
  • 1
  • 11
  • 23
  • Still, I don't see how the version with 'int' makes any difference? We replaced 'void' type with 'int'. The question still stands, what is so special about 'int' and why do you need that default value for it? –  Mar 06 '20 at 09:50
  • @Jarod42 sorry, missed that, fixed – calynr Mar 06 '20 at 10:50