Why does SFINAE not work with std::enable_if_t?

1.5k views Asked by At
#include <vector>
#include <list>
#include <iostream>

template
<
    typename T,
    template<typename, typename = std::allocator<T>> class C,
    typename = std::enable_if_t
    <
    std::is_same<std::vector<T>, C<T>>::value
    >
>
void f(const C<T>& coll)
{
    std::cout << "vector" << std::endl;
}

template
<
    typename T,
    template<typename, typename = std::allocator<T>> class C,
    typename = std::enable_if_t
    <
    std::is_same<std::list<T>, C<T>>::value
    >
>
void f(const C<T>& coll)
{
    std::cout << "list" << std::endl;
}

int main()
{
        std::vector<int> c1{ 1, 2, 3 };
        std::list<int> c2{ 1, 2, 3 };       
        f(c1);
        f(c2);
}

Clang 3.8 complains:

error : template parameter redefines default argument: typename = std::enable_if_t

What's wrong in the code?

2

There are 2 answers

2
Klaus On BEST ANSWER

Why you need SFINAE? A simple overload is enough!

template <typename T>
void f(const std::vector<T>& coll)
{
    std::cout << "vector" << std::endl;
}

template < typename T>
void f(const std::list<T>& coll)
{
    std::cout << "list" << std::endl;
}

int main()
{
    std::vector<int> c1{ 1, 2, 3 };
    std::list<int> c2{ 1, 2, 3 };
    f(c1);
    f(c2);
}

And if you really want to use SFINAE ( with no sense in that case ) you can fix your definition with:

template
<   
typename T,
         template<typename, typename = std::allocator<T>> class C,
         typename std::enable_if_t
         <
         std::is_same<std::list<T>, C<T>>::value
         >* = nullptr
    >
void f(const C<T>& coll);

And why your definition did not work can be found already here: SFINAE working in return type but not as template parameter

2
skypjack On

Default template parameters don't participate in function matching.
From the point of view of the compiler, when it's trying to look for a suitable function, both have the following signature:

template <typename T, template<typename, typename> class C, typename>
void f(const C<T>& coll);

Because of that, the second definition of f is actually a redefinition.

Consider a minimal example:

template<typename = void>
void f() {}

template<typename = char>
void f() {}

// ...

f<>();

How could the compiler figure out what f you want to invoke?
More or less the same happens in your code.

Move the enable_if_t to the return type and it will work as expected.
In this case, substitution will exclude one of the two definition because of an error (as expected from a sfinae expression).