【问题标题】:String literal in templates - different behavior of compilers模板中的字符串文字 - 编译器的不同行为
【发布时间】:2013-03-08 05:51:45
【问题描述】:

假设我们有以下代码:

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

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

Demonstration

gcc 4.7.2、clang 3.2、icc 13.0.1

对 `void foochar [4]>(char const (&) [4])'的未定义引用

MSVC-11.0

未解析的外部符号“void __cdecl foochar const [4]>(char const (&)[4])" (??$foo@$$BY03$$CBD@@YAXAAY03$$CBD@Z)

注意第一个输出中的char[4] 和第二个输出中的char const[4]

为什么?谁是对的?请您引用标准吗?

【问题讨论】:

  • 两人都在抱怨没有定义foo....
  • @UmNyobe 是的,我知道,只需要打印类型
  • @UmNyobe 我的意思是在这种情况下应该是什么类型 - char[4] 或 char const[4]?
  • @NikitaTrophimov:这是一个很好的问题,如果没有数组,这很简单,但我不确定const 应该出现在哪里(如果有的话)。
  • 我的猜测是 MSVC 只是在做额外的工作。

标签: c++ templates language-lawyer


【解决方案1】:

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 更符合 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
}

【讨论】:

  • 但是为什么std::add_const&lt;char [4]&gt;::typechar const[4]一样呢?这似乎并不明显 - 第一个是 4 个字符的 const 数组,而第二个是 4 个 const 字符的数组。
  • @interjay:参见 C++11 标准的 3.9.3/2:“[...] 任何应用于数组类型的 cv 限定符都会影响数组元素类型,而不是数组类型(8.3.4)。”
  • @interjay:我在上次更新我的答案时对此进行了扩展
【解决方案2】:

GCC 是正确的; VS的模板参数列表中的const不应该在那里:

[C++11: 14.8.2/3]: 执行此替换后,执行 8.3.5 中描述的函数参数类型调整。 [ 示例: “void ()(const int, int[5])” 的参数类型变为“void(*)(int,int*)”—结束示例] [注意:函数参数声明中的顶级限定符不会影响函数 类型,但仍会影响函数内函数参数变量的类型。 ——尾注] [示例:

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);
}

——结束示例]

(示例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 将被忽略以进行类型推导

【讨论】:

  • 我们都知道这一点,但它如何适用于本案?
  • @JamesKanze:const 不是函数类型的一部分,函数类型用于模板参数推导。因此,在这种情况下,T 不应是const,但在 OP 的示例中,MSVC 已经做到了。 ??????
  • 我确实认为我可能遗漏了几个步骤。 :( 不过,我想我会把它扔出去。
  • const OP 代码中函数类型的一部分。 OP 的代码与您给出的示例之间的区别在于,在 OP 的代码中,该函数引用了const,因此无法删除const
  • @LightnessRacesinOrbit 你在哪里看到的。 OP 的代码中没有顶级 const; intint const 在这种情况下被认为是相同的类型,但 int&amp;int const&amp; 不是。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-21
  • 2016-02-23
  • 1970-01-01
  • 2014-06-16
相关资源
最近更新 更多