Search code examples
c++sfinaeenable-if

Approaches to function SFINAE in C++


I am using function SFINAE heavily in a project and am not sure if there are any differences between the following two approaches (other than style):

#include <cstdlib>
#include <type_traits>
#include <iostream>

template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo()
{
    std::cout << "method 1" << std::endl;
}

template <class T, std::enable_if_t<std::is_same_v<T, double>>* = 0>
void foo()
{
    std::cout << "method 2" << std::endl;
}

int main()
{
    foo<int>();
    foo<double>();

    std::cout << "Done...";
    std::getchar();

    return EXIT_SUCCESS;
}

The program output is as expected:

method 1
method 2
Done...

I have seen method 2 used more often in stackoverflow, but I prefer method 1.

Are there any circumstances when these two approaches differ?


Solution

  • I have seen method 2 used more often in stackoverflow, but I prefer method 1.

    Suggestion: prefer method 2.

    Both methods work with single functions. The problem arises when you have more than a function, with the same signature, and you want enable only one function of the set.

    Suppose that you want enable foo(), version 1, when bar<T>() (pretend it's a constexpr function) is true, and foo(), version 2, when bar<T>() is false.

    With

    template <typename T, typename = std::enable_if_t<true == bar<T>()>>
    void foo () // version 1
     { }
    
    template <typename T, typename = std::enable_if_t<false == bar<T>()>>
    void foo () // version 2
     { }
    

    you get a compilation error because you have an ambiguity: two foo() functions with the same signature (a default template parameter doesn't change the signature).

    But the following solution

    template <typename T, std::enable_if_t<true == bar<T>(), bool> = true>
    void foo () // version 1
     { }
    
    template <typename T, std::enable_if_t<false == bar<T>(), bool> = true>
    void foo () // version 2
     { }
    

    works, because SFINAE modify the signature of the functions.

    Unrelated observation: there is also a third method: enable/disable the return type (except for class/struct constructors, obviously)

    template <typename T>
    std::enable_if_t<true == bar<T>()> foo () // version 1
     { }
    
    template <typename T>
    std::enable_if_t<false == bar<T>()> foo () // version 2
     { }
    

    As method 2, method 3 is compatible with selection of alternative functions with same signature.