Search code examples
c++templatesc++14sfinaeenable-if

SFINAE-based overloads conflict


#include <type_traits>

struct A {};

template <class T, class = std::enable_if_t<std::is_same_v<T, A>>>
void foo(T t) {}

template <class T, class = std::enable_if_t<!std::is_same_v<T, A>>>
void foo(T t) {}

int main()
{
    foo(A{});
    foo(0);
    return 0;
}

I see a compilation error for the second definition of foo():

test.cpp:9:6: error: redefinition of ‘template<class T, class> void foo(T)’

Looks like this is because SFINAE resolution happens on the later stages.

As a workaround I can add one more template parameter to the second foo() by changing it to:

template <class T, class = std::enable_if_t<!std::is_same_v<T, A>>, class = void> void foo(T t) {}

Then the error gone. But I am wondering is there more idiomatic way to handle such conflicting overloads? For two overloads it is enough to add one extra dummy template parameter but if we have many such overloads then the code would become more cluttered and the purpose of those extra parameters is unclear from the first glance to a reader.


Solution

  • But I am wondering is there more idiomatic way to handle such conflicting overloads?

    By example

    template <class T, std::enable_if_t<std::is_same<T, A>{}> * = nullptr>
    void foo(T t) {}
    
    template <class T, std::enable_if_t<!std::is_same<T, A>{}> * = nullptr>
    void foo(T t) {}
    

    But I find more clearer in this way

    template <class T>
    std::enable_if_t<std::is_same<T, A>{}> foo(T t) {}
    
    template <class T>
    std::enable_if_t<!std::is_same<T, A>{}> foo(T t) {}
    

    You can also add a second, defaulted, parameter.

    template <class T>
    void foo(T t, std::enable_if_t<std::is_same<T, A>{}> * = nullptr) {}
    
    template <class T>
    void foo(T t, std::enable_if_t<!std::is_same<T, A>{}> * = nullptr) {}