我使用基于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文档,它明确提到了部分特化)
我是否误解了上述文件中的要点,或者这真的是一个灰色区域?
答案 0 :(得分:15)
我想争辩说,由于措辞缺陷,标准不支持部分专业化的SFINAE。让我们从[temp.class.spec.match]开始:
如果是模板参数,则部分特化匹配给定的实际模板参数列表 部分特化可以从实际模板参数列表中推导出(14.8.2)。
并且,从[temp.deduct],SFINAE条款:
如果替换导致无效的类型或表达式,则类型推导失败。无效的类型或表达式 如果使用替代参数编写,则需要诊断的格式错误。 [ 注意: 如果不需要诊断,程序仍然是不正确的。访问检查是替换的一部分 处理。 -end note]只有函数类型及其模板参数类型的直接上下文中的无效类型和表达式才会导致演绎失败。
来自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
推导为char
。 sizeof(char) > sizeof(float)
,为false,enable_if_t<false, float>
是无效类型,因此类型扣除应该失败...但是,只能发生扣减失败
在函数类型及其模板参数类型
的直接上下文中
我们没有处理函数类型或函数模板参数类型,我们正在处理类模板参数类型。一个类不是一个函数,所以如果我们从字面上理解所有内容,SFINAE排除不应该适用 - 并且修改后的CWG示例应该导致硬错误。
然而,规则的精神似乎更像是:
只有扣减流程的直接上下文中的无效类型和表达式才会导致扣减失败。
我不知道具体排除类部分专业化演绎的原因是什么。此外,类模板部分特化的部分排序也看起来像函数。来自[temp.class.order]:
对于两个类模板的部分特化,第一个比第二个更专业,如果给定的话 跟随重写为两个功能模板,[...]
标准因此已经在下一节中展示了类模板部分特化和功能模板之间的对偶性。事实上,这仅适用于部分特化排序,而不是在部分特化参数推导期间的替换失败,这使我感到有缺陷。
†示例本身是X<double, float>
。但这实际上并没有证明或要求SFINAE,因为任何地方都不会有替代失败。