当前位置: 首页 > 知识库问答 >
问题:

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

欧阳元魁
2023-03-14

假设我们有以下代码:

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

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

集会示威

全球抄送 4.7.2, 叮当声 3.2, 硬拷贝 13.0.1

未定义对`void foo的引用

MSVC-11.0

无法解析的外部符号" void __cdecl foo

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

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

共有2个答案

湛文乐
2023-03-14

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

< code>[C 11: 14.8.2/3]:执行此替换后,将执行8.3.5中描述的功能参数类型调整。[示例:< code>"void ()(const int,int[5])"的参数类型变为< code>"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);
}

-结束示例]

(示例 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限定符进行类型推导

黄流觞
2023-03-14

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 引用的类型用于类型推断。[...]

这意味着类型演绎将尝试将< code>T const与< code>int(在案例1中)和< code>int const(在案例2中)进行匹配。在第二种情况下,用< code>int代替< code>T将产生一个完美的匹配,所以这很容易;在第一种情况下,我们让< code>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[]之外,这是一个示例,我的第一个示例在结构上是相同的。要了解为什么这种等价性成立,请考虑下面的断言(如预期的那样,它不会在任何编译器上触发):

// 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段还规定:

[…]任何类型的形式“N T的cv-限定符-seq数组”都被调整为“N cv-限定符-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限定符seq T的数组”具有cv限定类型;请参见3.9.3.-end Note]

现在很明显,这两个示例显示了相同的模式,因此应用相同的逻辑是有意义的。这将引导我们走上同样的推理道路。

执行类型演绎时,第一种情况下< code>T const与< code>char[4]匹配,第二种情况下与< code>char const[4]匹配。

在第二种情况下,T = char[4]产生一个完美的匹配,因为T常量在替换后变成字符常量[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,问题2,文章 我对Java字符串常量池有以下理解(如果我错了,请纠正我): 编译源代码时,编译器会在我们的程序中查找所有字符串文字(放在双引号中的那些),并在堆区域中创建不同的(无重复)对象,并在称为字符串常量池(方法区域内的区域)的特殊内存区域中维护它们的引用。任何其他字符串对象都是在运行时创建的。 假设我们的代码有以下语句: 当编译上述代码时, 第1行:在堆中创建一个S

  • 问题内容: 我有以下Ecma-Script-6代码 输出如下: 和 我已经能够在此处将字符串串联起来,那么使用模板文字的情形将是什么? 问题答案: 如果像问题示例中那样仅将模板文字与占位符(例如)一起使用,则结果与串联字符串相同。从主观上讲,它看起来更好并且更易于阅读,尤其是对于多行字符串或包含这两者的字符串,因为您不必再​​转义那些字符了。 可读性是一个很棒的功能,但是关于模板最有趣的是Tagg

  • 问题内容: 我有以下模板: 我在执行模板时传递了一个字符串。 但是,出现以下错误: 如何比较模板中的字符串? 问题答案: 是函数,而不是运算符。它以以下形式调用:(不是)。 您可以通过将操作数从的侧面移动到之后来修复模板:

  • 我正在学习如何通过OCA考试,并坚持这个java字符串池的概念。 考虑以下几点: 和在字符串中是相同的,在对象中也是相同的,因为它是相同的字符串文本,所以JVM在编译时将字符串池和。 现在,是在运行时计算的,因此应该返回一个新字符串。因此,应该为false,但事实并非如此。为什么? 我的一个理论是方法首先检查是否有空白需要删除,如果没有,则简单返回本身。这可以解释为什么s1==s3,但我不确定。

  • 问题内容: 是否可以将模板字符串创建为常规字符串 然后将其转换为模板字符串 没有,以及其他动态代码生成方式? 问题答案: 由于您的模板字符串必须动态地(在运行时)引用该变量,因此答案是: 否,没有动态代码生成是不可能的。 但这很简单:

  • Kotlin有一个很好的特性,叫做字符串模板。我真的很喜欢。 但是否可以在模板中设置任何格式?例如,我想在kotlin中设置字符串模板中的Double格式,至少要在小数分隔符后设置一些位数: