SFINAE和部分类模板特化

34

我已经使用基于SFINAE的方法有一段时间了,特别是通过std::enable_if来启用/禁用特定的类模板特化。

因此,在阅读描述所提出的void_t别名/检测习惯时,我感到有些困惑:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf

第4节讨论了该成语的有效性,并提到了一次关于SFINAE在部分类模板特化中适用性的争论(Richard Smith指出标准缺乏关于此主题的措辞)。在本节末尾,提到了以下CWG问题。

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2054

这里再次声明,标准并未明确允许在该问题中复制的示例。

我有些困惑,因为在偏特化中使用enable_if,例如,在相当长的一段时间内已经是标准实践(例如,请参见Boost文档,其中明确提到了偏特化)。

我是否误解了上述文件中的要点,还是这确实是一个灰色地带?


4
这个论点似乎相当有道理,既然标准没有规定,每个实现都按照你想要的方式处理,并且它很可能会以符合现状的方式得到解决。 - Yakk - Adam Nevraumont
@Yakk 但是我有点惊讶地看到它在像Boost和stackoverflow这样的圈子中被推广为一种可移植的做事方式。这是一种早期形式的技术,可以追溯到C++03,似乎很奇怪它没有被标准化在C++11/C++14中。 - bluescarni
3
好的,可能没有人注意到。 - Yakk - Adam Nevraumont
1
我重新阅读了标准的相关部分,似乎这是需要明确澄清的问题。问题在于,在有关类模板规范的第14.5.5.1/2节中,基本上是说“请参阅14.8.2”以获取实际规则。确实,14.8.2包括所有SFINAE内容,但那是一个关于函数模板而不是类模板的章节。对我来说,看起来他们不想重复自己,因此进行了重定向 - 但听起来相当笨拙。 - bluescarni
@Yakk忘了感谢你的回复,你说的很有道理 :) - bluescarni
1个回答

19
我认为由于措辞上的缺陷,标准不支持部分特化中的SFINAE。让我们从[temp.class.spec.match]开始:
如果给定的实参模板列表可以推导出部分特化的模板参数,则该部分特化与之匹配。
而根据[temp.deduct]中的SFINAE条款:
如果替换导致无效的类型或表达式,则类型推断失败。 无效的类型或表达式是指,如果使用替换后的参数编写,则会产生需要诊断的语法错误。[注意:如果不需要诊断,则程序仍然无效。 访问检查是替换过程的一部分。-结束说明]只有在函数类型及其模板参数类型的直接上下文中的无效类型和表达式才会导致推断失败。
CWG的略微修改的示例如下:
template <class T, class U> struct X   {
    typedef char member;
};

template<class T> struct X<T,
     typename enable_if<(sizeof(T)>sizeof(
 float)), float>::type>
{
    typedef long long member;
};

int main() {
    cout << sizeof(X<char, float>::member);
}

X上进行名称查找,找到主要内容,其中T == char,U == float。我们查看一个部分特化,并查看它是否“匹配”-这意味着模板参数“可以推导出来”,也就是说:

+-------------+--------+-------------------------------------------------+
|             | arg1     arg2                                            |
+-------------+--------+-------------------------------------------------+
| deduce T in | T      | enable_if_t<(sizeof(T) > sizeof(float), float>  |
| from        | char   | float                                           |
+-------------+--------+-------------------------------------------------+

正常的模板推导规则适用。第二个“参数”是一个不可推导的上下文,所以我们将T 推导为charsizeof(char) > sizeof(float) 是假的,并且 enable_if_t<false, float> 是一种无效类型,因此类型推导应该失败...但是,只有在函数类型及其模板参数类型的直接上下文中才会发生推导失败。
我们没有处理函数类型或函数模板参数类型,而是处理类模板参数类型。类不是函数,因此如果我们按照字面意思理解,SFINAE排除应用于类时应该不适用,这样修改后的CWG示例应该导致硬错误。
然而,规则的精神似乎更接近于:

仅在推导过程直接上下文中的无效类型和表达式才能导致推导失败。

我不知道特别排除类部分特化推导的原因是什么。此外,类模板部分特化的部分排序看起来也像函数。从[temp.class.order]中可以看到:

对于两个类模板部分特化,如果给出以下的重写为两个函数模板,则第一个比第二个更特化,[...]

因此,标准在下一节就展示了类模板部分特化和函数模板之间的双重性。这仅适用于部分特化排序,而不是在部分特化参数推导期间的替换失败,我认为这是一个缺陷。

示例本身是X<double, float>。但是,实际上这并不演示或需要SFINAE,因为任何地方都不会有替换失败。


自回答撰写以来有任何变化吗? - Evg
1
@Evg 我不这么认为。替换失败和直接上下文通常相当不充分地说明了。 - Barry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接