首页 > 解决方案 > 将模板参数从类型更改为非类型如何使 SFINAE 工作?

问题描述

cppreference.com上的文章std::enable_if

注释
一个常见的错误是声明两个仅在默认模板参数上有所不同的函数模板。这是非法的,因为默认模板参数不是函数模板签名的一部分,并且用相同的签名声明两个不同的函数模板是非法的。

/*** WRONG ***/

struct T {
    enum { int_t,float_t } m_type;
    template <
        typename Integer,
        typename = std::enable_if_t<std::is_integral<Integer>::value>
    >
    T(Integer) : m_type(int_t) {}

    template <
        typename Floating,
        typename = std::enable_if_t<std::is_floating_point<Floating>::value>
    >
    T(Floating) : m_type(float_t) {} // error: cannot overload
};

/* RIGHT */

struct T {
    enum { int_t,float_t } m_type;
    template <
        typename Integer,
        typename std::enable_if_t<std::is_integral<Integer>::value, int> = 0
    >
    T(Integer) : m_type(int_t) {}

    template <
        typename Floating,
        typename std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
    >
T(Floating) : m_type(float_t) {} // OK
};

 

我很难理解为什么该*** WRONG ***版本不能编译而该*** RIGHT***版本可以编译。解释和例子对我来说是货物崇拜。上面所做的只是将类型模板参数更改为非类型模板参数。对我来说,这两个版本都应该是有效的,因为它们都依赖于std::enable_if<boolean_expression,T>一个名为 的 typedef 成员type,并且std::enable_if<false,T>没有这样的成员。替换失败(这不是错误)应该导致两个版本。

查看标准,它在 [temp.deduct] 中说

当引用函数模板特化时,所有模板参数都应具有值

后来那个

如果没有推导出模板实参并且其对应的模板形参具有默认实参,则模板实参通过将前面模板形参确定的模板实参替换为默认实参来确定。如果替换导致无效类型,如上所述,类型推导失败。

这种类型的扣除失败不一定是错误,这就是 SFINAE 的全部意义所在。

为什么将*** WRONG ***版本中的 typename 模板参数更改为非 typename 参数会使*** RIGHT ***版本“正确”?

标签: c++templateslanguage-lawyertemplate-meta-programmingsfinae

解决方案


主要是因为[temp.over.link]/6没有讲模板默认参数:

两个模板头是等价的,如果它们的模板参数列表具有相同的长度,对应的模板参数是等价的,如果有一个requires-clause,它们都具有 requires-clauses 并且对应的约束表达式是等价的。在以下条件下,两个模板参数是等效的:

  • 他们声明相同类型的模板参数,

  • 如果其中任何一个声明了模板参数包,它们都会这样做,

  • 如果他们声明非类型模板参数,他们有等价的类型,

  • 如果他们声明模板模板参数,他们的模板参数是等价的,并且

  • 如果其中任何一个是用qualified-concept-name声明的,那么它们都是,并且qualified-concept-names是等价的。

然后通过[temp.over.link]/7

如果两个函数模板在相同的范围内声明、具有相同的名称、具有等效的模板头,并且具有使用上述规则等效的返回类型、参数列表和尾随要求子句(如果有),则它们是等效的比较涉及模板参数的表达式。

...您的第一个示例中的两个模板是等效的,而您的第二个示例中的两个模板不是。因此,您的第一个示例中的两个模板声明了相同的实体,并导致[class.mem]/5的结构不正确:

一个成员不得在成员规范中声明两次,...


推荐阅读