【问题标题】:Why does enable_if<>* = nullptr work when enable_if<> = void doesn't?为什么 enable_if<>* = nullptr 有效,而 enable_if<> = void 无效?
【发布时间】:2023-04-05 16:31:01
【问题描述】:

基本问题陈述

我正在学习 SFINAE。我尝试了一个极其简单的enable_if

// 1: A foo() that accepts arguments that are derived from Base
template <typename T, typename Enable = enable_if_t<std::is_base_of_v<Base, T>>>
void foo(T thing) {
    std::cout << "It's a derived!" << std::endl;
}

// 2: A foo() that accepts all other arguments
template <typename T, typename Enable = enable_if_t<!std::is_base_of_v<Base, T>>>
void foo(T thing) {
    std::cout << "It's not a derived." << std::endl;
}

编译器抱怨foo 是多重定义的。互联网告诉我这是因为在检查函数签名时模板参数不相关。

我尝试过的变体

在我尽可能地进行最基本的元编程的过程中,我开始在问题上抛出语法。以下是我为 enable_if 语句尝试过的事情的列表(反转语句,即 !std::is_base_of 相同,但为简洁起见省略):

匿名类型,无typename,等于0

https://en.cppreference.com/w/cpp/types/enable_if 告诉我我在上面所做的事情是错误的。但它的建议(在第一个注释块下找到)相当隐秘,更重要的是,它也无法编译。

std::enable_if_t&lt;std::is_base_of_v&lt;Base, T&gt;&gt; = 0

匿名类型,无typename,等于void

考虑到如果我正在使用类型进行编程,使用类型将是一个明智的选择,因此我尝试将模板默认为void。没有骰子。

std::enable_if_t&lt;std::is_base_of_v&lt;Base, T&gt;&gt; = void

匿名类型,是typename,等于void

当我们向它抛出语法时,如果我将此模板参数默认为一个类型,我不应该使用 typename 关键字吗?

typename std::enable_if_t&lt;std::is_base_of_v&lt;Base, T&gt;&gt; = void

最终的结果,哦,显然是这样的

typename enable_if_t&lt;std::is_base_of_v&lt;Base, T&gt;, T&gt;* = nullptr

我已经问过我认识的每个人为什么这有效,而我的其他变体却没有,他们同样感到困惑。我不知所措。更令人困惑的是,如果我将这种类型命名为(例如typename Enable = ...),它将无法编译。

如果有更熟悉 TMP 和 enable_if 的人向我解释一下,我将不胜感激:

  1. 为什么将enable_if 声明为指向类型的指针并将其默认为nullptr 有效?
  2. 默认enable_if的语义规则是什么?
  3. enable_if产生的命名类型的语义规则是什么?
  4. 有没有我可以使用的参考来清楚地解释这个和其他类似的规则在模板领域?

非常感谢。

【问题讨论】:

  • void 不是= 左侧的非类型模板参数的有效值“如果我命名此类型” - 它不是类型, typename enable_if_t&lt;std::is_base_of_v&lt;Base, T&gt;, T&gt;* 将是一个指向 T 值的指针,它需要一个有效的指针值作为初始值设定项,而不是某种类型
  • cppreference 上的示例确实可以编译。您在将其放入代码时更改了它,方法是选择不具有 0 作为有效初始化程序的类型。

标签: c++ templates c++17 enable-if


【解决方案1】:

您只是设置模板类型参数的值的第一组变体。模板类型参数具有不同值的两个重载会发生冲突,因为它们都属于 template&lt;class,class&gt; 并且具有相同的函数参数。

非类型模板参数情况,如果您最终拥有一个 void 类型的模板非类型参数,则使用原始启用的情况。那是非法的;各种错误信息是非法的各种方式。

当你添加一个星号时,当 enable if 子句通过时,它是一个 void 指针类型的模板非类型参数。

当它失败时,它根本不是一个论点。

与 nullptr 情况等效的是:

std::enable_if_t<std::is_base_of_v<Base, T>, bool> = true

当子句为真时,启用 if 计算结果为 bool,我们得到:

bool = true

bool 类型的模板非类型参数,默认为 true。当子句(子句的基础)为假时,我们得到 SFINAE 失败;那里没有模板类型或非类型参数。


对于class Whatever = enable_if 案例,我们正在尝试基于模板参数的默认值的 SFINAE。这会导致签名冲突,因为如果在重载解决期间(在同一阶段)发现签名必须是唯一的。

对于enable = value 案例,我们正在尝试基于if 的SFINAE,那里有一个模板非类型参数。失败时,没有要比较的签名,因此不会发生冲突。

剩下的就是让语法简单漂亮。

现在,Concepts 已经过时了,所以不要爱上它的语法。

【讨论】:

    猜你喜欢
    • 2017-11-14
    • 1970-01-01
    • 2012-12-07
    • 2020-03-09
    • 2011-07-05
    • 2012-01-15
    • 2013-12-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多