假设我们有以下代码:
template <typename T>
void foo(const T&);
int main()
{
foo("str");
}
gcc 4.7.2,clang 3.2,icc 13.0.1
未定义引用`void foo&lt; char [4]&gt;(char const(&amp;)[4])'
MSVC-11.0
未解析的外部符号“void __cdecl foo&lt; char const [4]&gt;(char const(&amp;)[4])“(?? $ foo @ $$ BY03 $$ CBD @@ YAXAAY03 $$ CBD @ Z)
在第一个输出中注意char[4]
,在第二个输出中注意char const[4]
。
为什么呢?谁是对的?你能引用这个标准吗?
答案 0 :(得分:5)
海湾合作委员会是对的。
让我们从一个稍微简单的例子开始,然后证明原始示例遵循相同的模式:
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-qualified
此处,替换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 []
之外,这是一个例子,我的第一个例子在结构上是相同的。要了解为什么这种等价性成立,请考虑下面的断言(不会像预期的那样触发任何编译器):
// 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数组的N T”都调整为“数组” N cv-qualifier-seq T“,类似于”T未知界限的数组“。可选的attribute-specifier-seq 附属于数组。 [例如:
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”
-end example] [注意:“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
}
答案 1 :(得分:1)
const
不应该存在:
[C++11: 14.8.2/3]:
执行此替换后,将执行8.3.5中描述的函数参数类型调整。 [示例:参数类型“void ()(const int, int[5])”
变为“void(*)(int,int*)”
。 -end example] [注意:函数参数声明中的顶级限定符不会影响函数 类型但仍会影响函数中函数参数变量的类型。 -end note] [示例:template <class T> void f(T t); template <class X> void g(const X x); template <class Z> void h(Z, Z*); int main() { // #1: function type is f(int), t is non const f<int>(1); // #2: function type is f(int), t is const f<const int>(1); // #3: function type is g(int), x is const g<int>(1); // #4: function type is g(int), x is const g<const int>(1); // #5: function type is h(int, const int*) h<const int>(1,0); }
-end example]
(例4是相关的。)
[C++11: 14.8.2/5]:
结果替换和调整后的函数类型用作模板参数推导的函数模板类型。 [..]
也可能相关:
从函数调用中推导模板参数
[C++11: 14.8.2.1/2]:
如果P
不是参考类型:
- 如果
A
是数组类型,则使用数组到指针标准转换(4.2)生成的指针类型代替A
进行类型推导;否则,- 如果
A
是函数类型,则使用函数到指针标准转换(4.3)生成的指针类型代替A
进行类型推导;否则,- 如果
A
是cv限定类型,则A
类型的顶级 cv-qualifiers 会因类型扣除而被忽略