GCC 是对的。
我们从一个稍微简单一点的例子开始,后面证明原来的例子也遵循同样的模式:
template<typename T>
void bar(T const&)
{
// Shall not fire
static_assert(std::is_same<T, int>::value, "Error!");
}
int main()
{
int x = 0;
bar(x); // 1 - Assertion won't fire
int const y = 0;
bar(y); // 2 - Assertion won't fire
}
这里发生了什么?首先,根据 § 14.8.2.1/3:
[...] 如果 P 是引用类型,则 P 所引用的类型用于类型推导。 [...]
这意味着类型推导将尝试匹配 T const 与 int(在情况 1 中)和 int const(在情况 2 中)。在第二种情况下,将int 替换为T 将产生完美匹配,所以这很容易;在第一种情况下,我们让const 开始进行完美匹配。但这就是第 14.8.2.1/4 条发挥作用的地方:
[...] 如果原来的P是引用类型,那么推导出来的A(即引用所指的类型)可以是
比转换后的 A 更符合 cv 条件。[...]
在这里,将int 替换为T 可以得到推导出的int const,它比int(参数x 的类型)更符合cv 条件。但这是可以接受的,因为上面的 § 14.8.2.1/4,所以即使在这种情况下,T 也被推断为int。
现在让我们处理您的原始示例(稍作调整,但我们最终会使用原始版本):
template<typename T>
void bar(T const&)
{
// Does not fire in GCC, fires in VC11. Who's right?
static_assert(std::is_same<T, char[4]>::value, "Error!");
}
int main()
{
char x[] = "foo";
bar(x);
char const y[] = "foo";
bar(y);
}
除了我将int 替换为char [] 之外,这是一个示例,我的第一个示例在结构上是相同的。要了解为什么这种等价性成立,请考虑下面的断言(正如预期的那样,它不会在 any 编译器上触发):
// Does not fire
static_assert(
std::is_same<
std::add_const<char [4]>::type,
char const[4]
>::value, "Error");
C++11 标准在第 3.9.3/2 段中规定了这种行为:
应用于数组类型的任何 cv 限定符都会影响数组元素类型,而不是数组类型 (8.3.4)。
第 8.3.4/1 段还规定:
[...] 任何形式的“cv-qualifier-seq array of N T”调整为“array
N cv-qualifier-seq T”,类似地用于“T 的未知边界数组”。可选的属性说明符序列
属于数组。 [ 例子:
typedef int A[5], AA[2][3];
typedef const A CA; // type is “array of 5 const int”
typedef const AA CAA; // type is “array of 2 array of 3 const int”
—结束示例 ] [ 注意:“N cv-qualifier-seq T 数组”具有 cv 限定类型;见 3.9.3。 ——尾注]
由于现在很清楚这两个示例展示了相同的模式,因此应用相同的逻辑是有意义的。这将引导我们走上同样的推理路径。
在执行类型推断时,T const 在第一种情况下与char[4] 匹配,在第二种情况下与char const[4] 匹配。
在第二种情况下,T = char[4] 产生完美匹配,因为替换后 T const 变为 char const[4]。在第一种情况下,推导出的 A 再次比原始 A 更符合 cv 要求,因为用 char[4] 代替 T 得到 char const[4]。但话又说回来,这是 14.8.2.1/4 所允许的,所以 T 应该被推导出为 char[4]。
最后,回到你原来的例子。由于字符串文字"str"也有char const[4]类型,所以T应该推导出为char [4],也就是说GCC是对的:
template<typename T>
void foo(T const&)
{
// Shall not fire
static_assert(std::is_same<T, char[4]>::value, "Error!");
}
int main()
{
foo("str"); // Shall not trigger the assertion
}