【问题标题】:Approaches to function SFINAE in C++在 C++ 中函数 SFINAE 的方法
【发布时间】:2020-04-15 19:30:18
【问题描述】:

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

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

【问题讨论】:

标签: c++ sfinae


【解决方案1】:

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

建议:首选方法2。

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

假设您要启用foo(),版本1,当bar&lt;T&gt;()(假设它是constexpr 函数)是true,而foo(),版本2,当bar&lt;T&gt;()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与选择具有相同签名的替代函数兼容。

【讨论】:

  • 感谢您的精彩解释,从现在开始我会更喜欢方法 2 和 3 :-)
  • “默认模板参数不会更改签名” - 这与您的第二个变体有何不同,它也使用默认模板参数?
  • @Eric - 说起来不简单...我想其他答案可以更好地解释这一点...如果 SFINAE 启用/禁用默认模板参数,foo() 函数在您调用它时仍然可用带有显式的第二个模板参数(foo&lt;double, double&gt;(); 调用)。如果仍然可用,则与其他版本存在歧义。使用方法 2,SFINAE 启用/禁用第二个参数,而不是默认参数。因此,您不能将其称为解释参数,因为存在不允许第二个参数的替换失败。所以版本不可用,所以没有歧义
  • 方法 3 的另一个优点是通常不会泄漏到符号名称中。变体 auto foo() -&gt; std::enable_if_t&lt;...&gt; 通常有助于避免隐藏函数签名并允许使用函数参数。
  • @max66:所以关键是如果提供了参数并且不需要默认值,那么模板参数默认值中的替换失败不是错误?
【解决方案2】:

除了max66's answer之外,另一个更喜欢方法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;
}

Live demo here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-04
    • 1970-01-01
    • 2020-11-01
    • 1970-01-01
    • 2018-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多