字符串字面值在编译器的不同行为
String literal in different behavior of compilers
假设我们有以下代码:
template <typename T>
void foo(const T&);
int main()
{
foo("str");
}
示范gcc 4.7.2,叮当声3.2,icc 13.0.1
定义引用的空隙foo<<strong>字符 [4]> (const char(,)[4])的
msvc - 11.0
无法解析的外部符号"void __cdecl foo<<strong>char const [4]>(char常量(,)[4])"(? ? foo@ BY03 CBD@@YAXAAY03美元美元美元美元CBD@Z)
注意第一个输出是char[4]
,第二个输出是char const[4]
。
为什么?谁是对的?你能报一下标准吗?
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.[…]
这里,将T
替换为int
会得到一个推导出来的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);
}
除了我用char []
代替int
这一事实之外,这个例子和我的第一个例子在结构上是相同的。要了解为什么这种等价成立,请考虑下面的断言(如预期的那样,它不会在任何编译器上触发):
// 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 "of N cv-qualifier-seq T",对于"数组的未知边界的T"也是类似的。可选的属性指定符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-qualified类型;3.9.3见。-end note]
既然现在很清楚这两个例子显示了相同的模式,那么应用相同的逻辑是有意义的。这将引导我们通过相同的推理路径。
在执行类型推导时,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
}
GCC是正确的;在VS的模板参数列表中不应该有const
:
[C++11: 14.8.2/3]:
替换完成后,将执行8.3.5中描述的函数参数类型调整。[示例:“void ()(const int, int[5])”
的参数类型变为“void(*)(int,int*)”
。 -end example][注意:函数参数声明中的顶级限定符不影响函数类型,但仍然影响函数内函数参数变量的类型。端注)(例子: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限定符进行类型推导
- VB6和c++布尔字面值
- 为什么添加两个字符串字面值不使用操作符+
- 在编译时定义字符串/char字面值
- c++的字符串字面值如何存储在内存中?
- 为什么可以向字符串字面值添加整数?
- 用户定义字面值如何与数字分隔符一起使用
- 可修改字符串字面值的用例
- c++ constexpr vs宏,字符串字面值vs整数
- 字符串对字符串字面值的优化不够
- 是否有可能合法地重载字符串字面值和const char*
- UnicodeString /字符串字面值vs十六进制值
- 为什么将指针的内容修改为字符串字面值是错误的?
- 可变char模板的用户定义字面值
- c++: Std::cout缓冲区错误?对字符串变量和字符串字面值使用std::cout导致输出混乱
- 不能定义用户定义的字面值
- C/ c++预处理器中的宏参数字符串化为宽字符串字面值
- 如何在c++中编写八进制浮点字面值
- 是否有可能获得包含字面值的字符串的长度
- 试图对初始化为字符串字面值的char指针的未定义行为进行推理
- 字符串字面值在编译器的不同行为