50

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?

keith
  • 5,122
  • 3
  • 21
  • 50
  • How do you run this program? [It won't compile for me.](http://coliru.stacked-crooked.com/a/c6e0966c7f83000b) – alter_igel Dec 24 '19 at 21:40
  • @alter igel it will need a C++17 compiler. I used MSVC 2019 to test this example but I mainly work with Clang. – keith Dec 24 '19 at 21:58
  • Related: [why-should-i-avoid-stdenable-if-in-function-signatures](https://stackoverflow.com/questions/14600201/why-should-i-avoid-stdenable-if-in-function-signatures) and C++20 introduces also new ways with concept :-) – Jarod42 Dec 24 '19 at 22:51
  • @Jarod42 Concepts are one of most needed things for me from C++20. – val - disappointed in SE Dec 25 '19 at 11:17

2 Answers2

46

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.

max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    Thanks for the great explanation, I will prefer methods 2 & 3 from now on :-) – keith Dec 24 '19 at 22:42
  • _"a default template parameter doesn't change the signature"_ - how is this different in your second variant, which also uses default template parameters? – Eric Dec 25 '19 at 17:02
  • 1
    @Eric - Non simple to say... I suppose the other answer explain this better... If SFINAE enable/disable the default template argument, the `foo()` function remain available when you call it with an explicit second template parameter (the `foo();` call). And if remain available, there is an ambiguity with the other version. With method 2, SFINAE enable/disable the second argument, not the default parameter. So you can't call it explicating the parameter because there is a substitution failure that doesn't permit a second parameter. So the version is unavailable, so no ambiguity – max66 Dec 25 '19 at 17:21
  • 3
    Method 3 has the additional advantage of generally not leaking into the symbol-name. The variant `auto foo() -> std::enable_if_t<...>` is often useful to avoid hiding the function-signature and to allow using the function-arguments. – Deduplicator Dec 25 '19 at 17:35
  • @max66: so the key point is that substitution failure in a template parameter default is not an error if the parameter is supplied and no default is needed? – Eric Dec 25 '19 at 21:10
  • @Eric - yes... seems to me that you caught the point: a substitution failure for a template parameter default disables only the calls of the function that don't express the parameter but maintain available the function itself. Method 2 disable that function itself. – max66 Dec 25 '19 at 21:41
23

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

alter_igel
  • 6,899
  • 3
  • 21
  • 40