为什么必须将 const 添加到 constexpr 以进行字符串文字声明?

Why does const have to be added to constexpr for a string literal declaration?

此声明:

char constexpr *const s = "hello";

失败并出现此错误:

g++ -g -Wall -Werror -std=c++17 test.cc -o test
test.cc:8:31: error: ISO C++11 does not allow conversion from string literal to 'char *const' [-Werror,-Wwritable-strings]
char constexpr *const s = "hello";

但是如果我将 const 添加到 constexpr,编译器会很高兴:

char const constexpr *const s = "hello";

编译:

g++ -g -Wall -Werror -std=c++17 test.cc -o test
./test
hello

这对我来说似乎不直观。为什么const需要修饰constexpr? constexpr 不意味着 const 吗?如果它是一个编译器常量,那么在其他意义上它怎么不是常量呢?是否有可能某些东西是 constexpr 但以其他方式改变而不是恒定的?

这是一个最小的神螺栓:

https://godbolt.org/z/sSQMVa


更新:

StoryTeller 的回答是理解这一点的关键。我接受了他的回答,但我会在这里展开,以防其他人试图理解这一点。在与 const 交互时,我习惯将 const 视为应用于其左侧的项目。因此:

char a[] = "hello";
char * const s = a;
s[0] = 'H'; // OK
s = "there"; // Compiler error.

这里,char * const s 表示指针 s 是常量,而它取消引用的字符是可修改的。另一方面:

char const * s = "hello";
a[0] = 'H'; // Compiler error
s = "there"; // OK

在这种情况下,char const * s表示s指向的字符是const,而不是指针。

好的,大多数使用过 const 和指针的人都明白这一点。我被抛弃的地方是我认为 constexpr 也会以这种方式工作。也就是说,鉴于此:

char constexpr * const s = "hello";

我认为这意味着指针是 const(它是)并且字符本身是 const 和 constexpr。但是语法不是那样工作的。相反,本例中的 constexpr:

因此,在这种情况下,没有对字符声明 const。事实上,如果我完全删除 constexpr,我会得到完全相同的错误:

char * const s = "hello"; // Produces same error as char constexpr * const s = "hello";

然而,这有效:

constexpr char const * s = "hello";

上面有我们想要的,意思是:

Doesn't constexpr imply const?

对象 上,在您的情况下 s 确实如此。应用 constexpr 的结果是对象

char *const s;

它仍然被声明为指向一个非常量对象。只有地址必须是常量表达式。这意味着它必须是一个具有静态存储持续时间的对象。

Is it possible for something to be constexpr but change in some other way such that is not constant?

没有。但话又说回来,它不是被声明的对象 constexpr 允许在此处更改。例如

static char foo[] = "abc"; // Not a constant array
constexpr  char * s  = foo; // But the address is still a valid initializer.

是一对有效的声明。

const 适用于它左边的东西,或者如果什么都没有则适用于它的右边。

char *const s = "hello"; 中,const 应用于 *,而不是 char,因此 s 是指向非const char 数据。但是,字符串文字 是 const char 数据(在这种情况下,"hello"const char[6])。您不能拥有指向实际上指向 const 数据的非常量数据的指针,这将允许 const 数据是可修改的,如果某些东西实际上试图修改数据,这是未定义的行为。这就是编译器错误所抱怨的。

因此,您需要一个指向 const char 数据的指针:

char const *const s = "hello";

或:

const char *const s = "hello";

constexpr 只是使 s 变量在编译时可用于计算。

static constexpr auto NONE = "none";

感谢 StoryTeller 的回答和 Firebush 的解释,让我受益匪浅。

这里的问题是关于 constarray ,我做了一些简单的测试,比如 Firebush。

关于数组,const 总是防止修改特定维度而不是所有内容,我认为这可能对某些人有帮助,所以我 post 在这里测试代码和注释。

char* const strs1[] = {"uu", "vv"}; // protected 1st dimension
const char* strs2[] = {"ww", "xx"}; // protected 2nd dimension
char* strs3[] = {"yy", "zz"};

strs1[0] = "aa"; // error, try to modify 1st dimension
strs1[0][0] = 'a';

strs2[0] = "aa";
strs2[0][0] = 'a'; // error, try to modify 2nd dimension

strs1 = strs3; // error, try to modify 1st dimension
strs2 = strs3; // error, try to modify 2nd dimension

道理都在上面,最大的遗憾是错过了一个简短而有效的几句话总结,让我们大家永远不会忘记const的用法。