在C ++中实现SFINAE的方法


40

我在项目中大量使用函数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...

我已经看到方法2在stackoverflow中使用得更频繁,但是我更喜欢方法1。

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


您如何运行该程序?它不会为我编译。
改变igel

@alter igel,它将需要C ++ 17编译器。我使用MSVC 2019来测试此示例,但我主要使用Clang。
基思

相关:为什么我应该避免在函数签名中出现易碎的迹象,并且C ++ 20还引入了新的概念:-)
Jarod42 '19

@ Jarod42从C ++ 20开始,概念对我来说是最需要的东西之一。
瓦尔说莫妮卡(Monica)恢复

Answers:


35

我已经看到方法2在stackoverflow中使用得更频繁,但是我更喜欢方法1。

建议:首选方法2。

两种方法都可以使用单个功能。当您拥有一个以上具有相同签名的功能,并且只想启用集合中的一个功能时,就会出现问题。

假设你想启用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修改了功能的签名。

不相关的观察:还有第三种方法:启用/禁用返回类型(显然,类/结构构造函数除外)

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与具有相同签名的替代功能的选择兼容。


1
感谢您的出色解释,从现在开始,我将更喜欢方法2和3 :-)
keith

“默认模板参数不会更改签名” -第二个变量也使用默认模板参数,这有何不同?
埃里克

1
@Eric-很难说...我想另一个答案可以更好地说明这一点...如果SFINAE启用/禁用默认模板参数,则foo()当您使用显式第二个模板参数(foo<double, double>();调用)调用该函数时,该函数仍然可用。如果仍然可用,则与其他版本存在歧义。使用方法2,SFINEE启用/禁用第二个参数,而不是默认参数。因此,您不能称其为参数重复,因为存在替换失败,不允许出现第二个参数。因此该版本不可用,因此没有歧义
max66

3
方法3的另一个优点是通常不会泄漏到符号名称中。该变体auto foo() -> std::enable_if_t<...>通常有助于避免隐藏功能签名并允许使用功能参数。
Deduplicator

@ max66:所以关键是模板参数默认值中的替换失败不是错误(如果提供了参数且不需要默认值)?
埃里克

21

除了max66的答案之外,更喜欢方法2的另一个原因是,使用方法1,您可以(偶然地)将显式类型参数作为第二个模板参数传递,并完全击败SFINAE机制。这可能是由于打字错误,复制/粘贴错误,或者是在较大的模板机制中的疏忽而发生的。

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

现场演示


好点子。该机制可以被劫持。
max66
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.