双重标准?为什么只对 char* const& a = "bla" 发出警告?

Double standard? Why only a warning for char* const& a = "bla"?

在尝试深入研究 等案例背后的机制后,我仍然不明白为什么下面代码中的第三行仅生成警告,而第二行是一个错误。

int main()
{
    const char* const& a = "bla"; // Valid code
    const char*& a2 = "bla"; // Invalid code
    char* const& a3 = "bla"; // Should be invalid but settles for a warning

    return 0;
}

我知道虽然 引用初始化 正在将 字符串文字 转换为指针引用,但它不应该删除任何 cv-qualifiers 对象具有,并且由于转换类型是 const char* const(从字符串文字 "bla" 转换而来,即 const char[4]),它似乎是与第二行相同的情况。唯一的区别是被删除的 const 属于 C 字符串本身而不属于指针。

在 GCC 8.2 和 Clang 6.0.0 上重现而不指定任何额外的一致性标志。

来自 gcc 的输出:

<source>:4:23: error: cannot bind non-const lvalue reference of type 'const char*&' to an rvalue of type 'const char*'
     const char*& a2 = "Some other string literal";
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~

<source>:5:23: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
     char* const& a3 = "Yet another string literal";

为什么现在的编译器符合第一种情况而不符合第二种情况?或者,这两种情况之间是否存在根本区别?

字符串文字是数组。 "bla" 的类型是 const char [4].

const char* const& a = "bla";

这是有效的,因为存在从 T []T * 的转换;在这种情况下,您会得到一个 const char * 右值。这个右值可以绑定到一个引用,因为它是对 const 的引用(它使临时对象保持活动状态等)。

const char*& a2 = "bla";

无效,因为您在此处尝试将临时值绑定到非常量引用。

char* const& a3 = "bla";

这是对常量的引用,但类型错误(它是指向字符的指针,而不是指向常量字符的指针)。此转换会删​​除 const 限定符,因此它应该是无效的。出于向后兼容的原因,一些 C++ 编译器允许这样做:在 C 中,字符串文字具有非 const 限定类型(即 "bla" 将是 char [4]),因此将其设为硬错误会破坏许多现有的代码。

即使在 C++ 中,这也曾经是合法的。在 C++11 之前,仍然允许将字符串文字分配给 char *(不是 const char *)变量(但已弃用)。

"double standard" 是因为从来不允许将非常量引用绑定到临时对象(C 甚至没有引用),所以那里没有向后兼容性问题。标准不区分"errors"和"warnings";对于任何给定的违反规则的情况,编译是否应该成功由编译器编写者自行决定。

两种情况都是错误的。但是,该标准不要求编译器拒绝格式错误的程序。所以,解决一个警告是完全符合标准的。

Or alternatively, is there a fundamental different I'm missing here between the two cases?

主要区别在于,将非常量左值引用绑定到右值从来都不是良构的,而隐式转换 const char*char* 在 C+ 之前一直是良构的+11。向后兼容性是允许后者的一个很好的论据。

让我们使用 EAST const 语法对其进行解构。

const 的规则是它总是应用于它左边的东西,除非它左边没有任何东西,在这种情况下它适用于紧邻右边的东西。用EAST const,我们总是把const写在右边。

那么让我们看一下代码:

const char* const& a = "bla"; // Valid code

变成

char const * const & a = "bla";

所以 char 是常量,无法更改。

指向字符的指针是常量,也不能改变。

总体:这是对无法更改的指针的引用,无法更改的字符。

"bla" 是一个 const C 风格的数组,它会立即衰减为一个 char const * const.

它是 "char const * const" 而不是 "char const *" 的原因是因为 "bla" 的地址是常量 - 字符串 "bla" 被编译到某处的执行代码中一个固定的位置,当加载到内存中时,将停留在该内存地址,直到程序终止。

所以现在我们有了除引用之外的匹配类型。

T &a = 某事;如果某些东西是类型 T 并且它有一个地址(它确实如此),它将始终有效。

再看第二个:

const char*& a2 = "bla"; 

EAST 常量语法:

char const * & a2 = "bla";

"bla" 是类型:

char const * const

这些不是匹配类型("bla"的内存位置是固定的)。

也许这段代码会更清楚:

char const *stringPtr = "hello";
char const *stringPtr2 = "world";

char const * &stringPtrRef = stringPtr;

std::cout << stringPtr << std::endl;

stringPtrRef = stringPtr2;

std::cout << stringPtr << std::endl;

这将在第一行打印 "Hello",在第二行打印 "World"。这是因为 stringPtr 指向的内容发生了变化。

由于 "bla" 的位置是固定的,我们无法构造对它的引用,因为 "bla" 的位置可以通过将对它的引用设置为其他内容来更改。这是不可能的。也没有我们可以用来强制它成为正确类型的可能的转换。

这就是为什么即使有警告也无法编译的原因。

我们来看第三个:

char* const& a3 = "bla";

这已经是 EAST const 格式。

with "char * const &" - 生成的引用虽然不允许更改内存位置,但允许您将 "bla" 修改为 "abc"。

也许在某些情况下,您实际上想这样做是为了在某些嵌入式系统上节省内存 space,其中 "bla" 仅用作初始化,而不会再使用。

消息有道理:

"warning: ISO C++ forbids converting a string constant to 'char*"

因为这与以下基本相同:

char const *s1 = "bla";
char *s2 = s1;

实际上编译时会出现带有正确编译器标志 (-fpermissive) 的警告。

即使没有 -fpermissive,我们也可以更改代码来执行转换并使其工作。

所以,我理解为什么它可以编译,但我认为这应该是一个错误。 ISO C++ 明确禁止它。我的意见:如果这真的是你想要做的,就需要演员表。