Approaches to function SFINAE in C++

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.


In addition to max66's answer, another reason to prefer method 2 is that with method 1, you can (accidentally) pass an explicit type parameter as the second template argument and defeat the SFINAE mechanism completely. This could happen as a typo, copy/paste error, or as an oversight in a larger template mechanism.

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

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

int main(){

    // works fine
    foo<int>();

    // ERROR: subsitution failure, as expected
    // foo<double>();

    // Oops! also works, even though T != int :(
    foo<double, double>();

    return 0;
}

Live demo here

Tags:

C++

Sfinae