大约一两年前,我读到了 C++ 中的 SFINAE 规则。他们特别指出,
以下类型错误是 SFINAE 错误:
...
尝试创建 void 数组、引用数组、函数数组、负大小数组、非整数大小数组或大小为零的数组
我决定在我的作业中使用这个规则,但是它不起作用。逐渐减少它,我来到了这个我不理解的小代码示例:
#include <iostream>
template<int I>
struct Char {};
template<int I>
using Failer = Char<I>[0];
template<int I>
void y(Failer<I> = 0) {
std::cout << "y<" << I << ">, Failer version\n";
}
template<int I>
void y(int = 0) {
std::cout << "y<" << I << ">, int version\n";
}
int main() {
y<0>();
y<1>();
y<2>();
y<3>();
}
而且,一些C++编译器似乎也不理解它。我创建了一个 Godbolt 示例,您可以在其中找到三种不同的编译器以不同的方式解决
y
歧义:
int
版本(这是我认为符合 SFINAE 规则的版本);Failer
版本。其中哪一个是正确的?到底发生了什么?
好问题。您看到的差异是由于编译器如何解释标准造成的。 C++ 标准确实规定,大小为零的数组会导致 SFINAE 错误。然而,这在某种程度上可以由编译器解释。
GCC:最严格的解释。将零大小数组视为错误,并且不允许 SFINAE 启动。
Clang:更严格地遵循标准。看到零大小数组,触发 SFINAE,并回退到
int
版本。
ICC:这绝对是意外行为,并且可能不符合标准。它不应该选择零大小数组版本。
就标准而言,Clang 是最接近预期的。 GCC 很严格,但本身并没有错,而 ICC 很可能是错误的。
对于可移植代码,最好不要依赖这种特定的 SFINAE 机制,因为编译器行为各不相同。