在 C++ 中实现 SFINAE 的方法

Approaches to function SFINAE in C++

我在一个项目中大量使用 SFINAE 函数,我不确定以下两种方法之间是否存在任何差异(除了样式):

#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;
}

程序输出符合预期:

method 1
method 2
Done...

我在Whosebug中看到方法2用的比较多,但是我更喜欢方法1

这两种方法有什么不同的情况吗?

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

建议:首选方法二

这两种方法都适用于单个函数。当您有多个具有相同签名的函数,并且您只想启用集合中的一个函数时,就会出现问题。

假设您想要启用 foo(),版本 1,当 bar<T>()(假装它是一个 constexpr 函数)是 true,并且 foo(),版本 2,当 bar<T>()false.

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
 { }

你得到一个编译错误,因为你有歧义:两个 foo() 具有相同签名的函数(默认模板参数不会更改签名)。

但是下面的解决方法

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
 { }

有效,因为 SFINAE 修改了函数的签名。

不相关的观察:还有第三种方法:enable/disable return 类型(显然 class/struct 构造函数除外)

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
 { }

与方法 2 一样,方法 3 与选择具有相同签名的替代函数兼容。

除了之外,更喜欢方法 2 的另一个原因是使用方法 1,您可以(不小心)将显式类型参数作为第二个模板参数传递,从而完全破坏 SFINAE 机制。这可能是拼写错误、copy/paste 错误,或者是对更大模板机制的疏忽。

#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