字符串字面值在编译器的不同行为

String literal in different behavior of compilers

本文关键字:字面值 编译器 字符串      更新时间:2023-10-16

假设我们有以下代码:

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 constint(在情况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限定符进行类型推导