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

12

假设我们有以下代码:

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

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

演示

gcc 4.7.2、clang 3.2、icc 13.0.1

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

MSVC-11.0

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

请注意,第一个输出中使用了 char[4],而第二个输出中使用了 char const[4]

为什么会这样?谁是正确的?能引用一下标准吗?


7
两者都在抱怨没有定义foo - UmNyobe
@NikitaTrophimov:这是一个好问题,如果没有数组,它就相当简单,但是如果有数组,我不确定const应该出现在哪里(如果需要的话)。 - Matthieu M.
我的猜测是MSVC只是在做额外的工作。 - UmNyobe
我的直觉告诉我gcc是正确的,因为const应用于T而不是它的一部分。让我们找出来... - Lightness Races in Orbit
@NikitaTrophimov:我的回答应该能澄清事情。 - Andy Prowl
显示剩余5条评论
2个回答

5

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更cv限定。 [...]

在这里,将int替换为T会给我们一个推导出的int const,它比参数x的类型int更有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限定符序列N个T的数组”调整为“N个cv限定符序列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

自从现在清楚这两个示例展示了相同的模式,应用相同的逻辑就是有意义的。这将引导我们通过完全相同的推理路径。
在执行类型推断时,在第一种情况下,将“T const”与“char[4]”匹配,在第二种情况下,将“T const”与“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<char [4]>::typechar const[4] 是相同的呢?这似乎并不明显 - 第一个是由 4 个字符组成的常量数组,而第二个是由 4 个常量字符组成的数组。 - interjay
@interjay:请参考C++11标准的3.9.3/2节:“[...] 应用于数组类型的任何cv限定符都会影响数组元素类型,而不是数组类型(8.3.4)。” - Andy Prowl
@interjay:我在最后一次更新答案时对此进行了一些扩展。 - Andy Prowl

1

GCC是正确的;VS模板参数列表中的const不应该出现:

[C++11: 14.8.2/3]: After this substitution is performed, the function parameter type adjustments described in 8.3.5 are performed. [ Example: A parameter type of “void ()(const int, int[5])” becomes “void(*)(int,int*)”. —end example ] [ Note: A top-level qualifier in a function parameter declaration does not affect the function type but still affects the type of the function parameter variable within the function. —end note ] [ 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-qualified 类型,则忽略 A 的顶层 cv-qualifiers 以进行类型推断

1
我们都知道这一点,但它如何适用于所讨论的情况呢? - James Kanze
1
const在OP的代码中是函数类型的一部分。 OP代码和您给出的示例之间的区别在于,在OP代码中,函数采用对const的引用,因此无法删除const - interjay
2
@LightnessRacesinOrbit 你在哪里看到的?OP的代码中没有顶层const;在这种情况下,int和int const被认为是相同的类型,但int&和int const&不是。 - James Kanze
@LightnessRacesinOrbit:除非我脑子出了问题,A - 即参数 - 是“str”,A 的类型是 const char[4],而参数 P 是 const T&(未命名参数)。 - MSalters
@NikitaTrophimov 远远不够。 - Lightness Races in Orbit
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接