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

This declaration:

char constexpr *const s = "hello";

Fails with this error:

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

But if I add const to constexpr, the compiler is happy:

char const constexpr *const s = "hello";

Compilation:

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

This seems unintuitive to me. Why does const need to decorate constexpr? Doesn't constexpr imply const? If it's a compiler constant, how is it not a constant in some other sense? Is it possible for something to be constexpr but change in some other way such that is not constant?

Here's a minimal godbolt:

https://godbolt.org/z/sSQMVa


Update:

StoryTeller's answer has the key to understanding this. I've accepted his answer, but I'll expand on it here in case that's helpful for someone else trying to understand this. When interacting with const, I'm used to thinking of const as applying to the item on the left of it. Thus:

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

Here, char * const s means that the pointer, s, is const while the characters it dereferences to are modifiable. On the other hand:

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

In this case, char const * s means that the characters that s points to are const, not the pointer.

OK, most people who have worked with const and pointers understand all that. Where I got thrown off is that I assumed that constexpr would work this way as well. That is, given this:

char constexpr * const s = "hello";

I thought that would mean that the pointer is const (it is) and that the characters themselves would be const and constexpr. But the syntax doesn't work that way. Rather, the constexpr in this case:

  • Does not apply to the characters, but rather...
  • applies to s itself, which is a pointer, and...
  • therefore is redundant with the const after the pointer.

Thus, in this case, there is no const being declared on the characters. Indeed, if I remove the constexpr entirely I get the exact same error:

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

This, however, works:

constexpr char const * s = "hello";

The above has what we want and it means:

  • The characters are const via const
  • And the pointer s is const and a compile time constant via constexpr


Solution 1:[1]

Doesn't constexpr imply const?

It does, on the object being declared, in your case s. The result of applying constexpr is the object

char *const s;

It's still declared to point at a non-const object. Only the address has to be a constant expression. Which means it must be to an object with static storage duration.

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

No. But then again, it's not the object being declared constexpr that is allowed to change here. For instance

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

Is a valid pair of declarations.

Solution 2:[2]

const applies to the thing on its left, or if nothing is there then to its right.

In char *const s = "hello";, the const is applied to the *, not to the char, so s is a const pointer to non-const char data. However, a string literal is const char data (in this case, "hello" is a const char[6]). You can't have a pointer to non-const data that is actually pointing at const data, that would allow the const data to be modifiable, which is undefined behavior if something actually tried to modify the data. That is what the compiler error is complaining about.

So, you need a pointer to const char data instead:

char const *const s = "hello";

Or:

const char *const s = "hello";

The constexpr just makes the s variable available for evaluation at compile-time.

Solution 3:[3]

Thanks for StoryTeller's answer and Firebush's explanation which teach me a lot.

And the problem lead me here is about const with array , I have done some simple test like Firebush.

Regard for array, const always prevent to modify specific dimension instead of all content which I think maybe help for some one, so I post test code and comments here.

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

The truth is all above, the biggest regret is miss a short and effective summary in several words to make all of us never forget the usage of const.

Solution 4:[4]

static constexpr auto NONE = "none";

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 oguz ismail
Solution 2
Solution 3 QooBee
Solution 4 Chand Priyankara