模板中的字符串文字 - 编译器的不同行为

时间:2013-03-19 13:56:12

标签: c++ templates language-lawyer

假设我们有以下代码:

template <typename T>
void foo(const T&);

int main()
{
   foo("str");
}

Demonstration

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]

为什么呢?谁是对的?你能引用这个标准吗?

2 个答案:

答案 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 constint(案例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)

海湾合作委员会是正确的; VS的模板参数列表中的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 会因类型扣除而被忽略
  •