为什么在字符串字面值声明中,constexpr 必须要添加 const?

22

这个声明:

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";

但如果我在constexpr前面加上const,编译器就会很高兴:

char const constexpr *const s = "hello";

编译:

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

对我来说,这似乎不直观。为什么const需要修饰constexpr?constexpr不意味着const吗?如果它是一个编译器常量,在某种意义上它不是一个常量吗?某物可能是constexpr但以某种其他方式发生变化,使它不再是常量吗?

这是一个极简的godbolt链接:

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。但是语法并不是这样工作的。相反,在这种情况下:

  • 并不适用于字符,而是适用于s本身,它是一个指针,因此...
  • 与指针后面的const重复了。

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

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

不过,这个是有效的:

constexpr char const * s = "hello";

上述代码中包含我们需要的信息,即:

  • 字符通过 const 修饰为常量
  • 指针 s 通过 constexpr 修饰为编译时常量且为常量指针

非常好的解释!谢谢。 - russoue
4个回答

13

constexpr是否不意味着const?

在您声明的对象上,也就是s上使用constexpr确实等同于const。应用constexpr的结果是该对象。

char *const s;

它仍被声明为指向非const对象。只需要地址是常量表达式,这意味着它必须是指向具有静态存储期的对象。

是否可能某些东西是constexpr的,但以其他方式更改,以至于不是常量?

不可能。但话说回来,在这里允许变化的并不是声明为constexpr的对象。例如

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

是有效声明对。


唉,我习惯于将const应用于左侧的事物。因此,通过char constexpr * const s = "hello";,我想要传达的是char数组是constexpr,而不是指针。但是从你指出的内容来看,constexpr并不是这样工作的,而是应用于指针。好的,这增加了警告的清晰度。确实,这与char * const s = "hello";相同的警告。 - firebush

3

const适用于它左边的东西,如果没有则适用于右边的东西。

char *const s = "hello";中,const适用于*,而不是char,因此s是一个指向非常量char数据的常量指针。然而,字符串字面值是常量字符数据(在本例中,"hello"const char[6])。你不能有一个指向非常量数据的指针,它实际上指向的是常量数据,这会允许修改常量数据,如果真的尝试修改数据,则会导致未定义行为。这就是编译器错误所抱怨的内容。

因此,你需要一个指向常量字符数据的指针:

char const *const s = "hello";

或者:

const char *const s = "hello";

constexpr只是使得s变量可以在编译时进行评估。


@bolov - 我猜他的意思是“constexpr不改变类型”,因为在这种情况下,类型已经是一个const类型。但是,是的...可能会被错误地理解。 - max66
为了避免人们被这个问题所困惑,我必须尊重地指出你最后一句话是不正确的。我也曾经因此感到困惑。constexpr确实意味着const并且会改变类型,但它令人困惑的是应用于指针而不是它所指向的字符,即使放在char之后。因此,constexpr char const * s = "hello";将s的类型从可修改指针更改为常量指针。也就是说,它在指针后面隐含了const。此外,它等同于char const constexpr * s = "hello";:即使应用于char之后,它也适用于指针。 - firebush

0

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

我来这里的问题是关于constarray的,我已经做了一些像Firebush一样的简单测试。

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

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的简短而有效的方法,让我们永远不会忘记它的用法。


-1
static constexpr auto NONE = "none";

“仅有代码的答案并不是高质量的答案”。虽然这段代码可能很有用,但您可以通过解释它为什么有效、如何有效、何时应该使用以及其限制是什么来改进它。请编辑您的答案,包括解释和相关文档链接。 - Stephen Ostermiller

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