I'm using GCC 14 and Clang 21. Why is the next C++23 code refused by GCC but Clang compiles without problems ? Both seems to support this functionality of C++20 and I'm using C++23. I think I've read somewhere that std::string
on some implementations uses a char[16]
internal array to prevent unnecessary heap allocations in the case of small strings. Might that be linked, based on the error GCC gives?
#include <string>
int main()
{
constexpr std::string chaine { "e" };
return 0;
}
ain.cpp: In function 'int main()':
main.cpp:6:40: error: 'std::string{std::__cxx11::basic_string<char>::_Alloc_hider{((char*)(& chaine.std::__cxx11::basic_string<char>::<anonymous>.std::__cxx11::basic_string<char>::<unnamed union>::_M_local_buf))}, 1, std::__cxx11::basic_string<char>::<unnamed union>{char [16]{'e', 0, '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000'}}}' is not a constant expression
6 | constexpr std::string chaine { "e" };
| ^
main.cpp:6:40: error: 'std::__cxx11::basic_string<char>(((const char*)"e"), std::allocator<char>())' is not a constant expression because it refers to an incompletely initialized variable
NB : MSVC also does not like this code, I got E0028 and C2131.
I'm using GCC 14 and Clang 21. Why is the next C++23 code refused by GCC but Clang compiles without problems ? Both seems to support this functionality of C++20 and I'm using C++23. I think I've read somewhere that std::string
on some implementations uses a char[16]
internal array to prevent unnecessary heap allocations in the case of small strings. Might that be linked, based on the error GCC gives?
#include <string>
int main()
{
constexpr std::string chaine { "e" };
return 0;
}
ain.cpp: In function 'int main()':
main.cpp:6:40: error: 'std::string{std::__cxx11::basic_string<char>::_Alloc_hider{((char*)(& chaine.std::__cxx11::basic_string<char>::<anonymous>.std::__cxx11::basic_string<char>::<unnamed union>::_M_local_buf))}, 1, std::__cxx11::basic_string<char>::<unnamed union>{char [16]{'e', 0, '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000'}}}' is not a constant expression
6 | constexpr std::string chaine { "e" };
| ^
main.cpp:6:40: error: 'std::__cxx11::basic_string<char>(((const char*)"e"), std::allocator<char>())' is not a constant expression because it refers to an incompletely initialized variable
NB : MSVC also does not like this code, I got E0028 and C2131.
Share Improve this question edited Mar 31 at 14:24 informaticienzero asked Mar 31 at 14:05 informaticienzeroinformaticienzero 1,8991 gold badge15 silver badges22 bronze badges 6- 1 VS compiles it fine for me: godbolt./z/d1We7x48r – ChrisMM Commented Mar 31 at 14:09
- Maybe this Jason Turner video is relevant? – PaulMcKenzie Commented Mar 31 at 14:22
- 2 "constexpr std::string" means that you can use a string inside a constexpr function, not that the string must be able to survive until runtime. It might do that anyway, sometimes. For example, in my MSVC the example works in Release mode, but Debug mode points out that a heap allocation is not allowed. – BoP Commented Mar 31 at 14:33
- 3 SSO doesn't actually allocate memory, I suspect some library allows constexpr string in this case. – 康桓瑋 Commented Mar 31 at 14:37
- Can't repro: godbolt./z/59Envhf1M did you enabled C++23 for all compilers? – Marek R Commented Mar 31 at 16:31
1 Answer
Reset to default 5This isn't a difference of GCC versus Clang; it's a difference of libstdc++ versus libc++. Godbolt:
#include <string>
int main() {
constexpr std::string a = "a";
constexpr std::string b = "a very long string that requires heap allocation no matter what";
static constexpr std::string c = "a";
static constexpr std::string d = "a very long string that requires heap allocation no matter what";
}
The problem with d
is that the value of a constexpr variable must be known at compile time but must still be usable at runtime. Since C++20, the compiler has a sort of "constexpr-time heap" available for use during constant evaluation; but since that heap lives inside the memory of the compiler process, things allocated on that heap aren't usable any more at runtime. That means you can't capture them into the values of constexpr variables.
static constexpr std::string d = "a very long string that requires heap allocation no matter what";
// not allowed, because what heap-allocation would it point to at runtime?
static_assert(std::string("a very long string that requires heap allocation no matter what").size() != 10);
// perfectly fine, because the heap-allocation is gone by runtime
So d
is invalid for that reason. b
is invalid for the same reason; the only difference with b
is that the string
object itself is now located on the stack frame of main
. It's still a constexpr variable — its value must be known at compile-time and usable at runtime — but its address is now basically randomized at runtime (depending on where main
's stack frame is allocated at runtime).
Now, c
is perfectly fine and everyone accepts it. That's because its value doesn't require any heap-allocation: it's stored in c
's small-string buffer. This means that when you call c.data()
or c.c_str()
, you get a pointer that actually points into the memory footprint of c
itself.
Now here's the difference between libstdc++ and libc++. libc++ (and MS STL too) store basically a boolean flag inside c
that tells the library whether to follow a pointer to a heap allocation or look at c
's small-string buffer. libstdc++ instead stores a copy of that .data()
pointer directly inside c
. When the string value is short, that data pointer holds the address of c
's small-string buffer. When the string value is lengthier, that data pointer holds the address of a heap-allocation.
This is fine for c
, because its small-string buffer is located at a fixed address ("address-of-c
plus maybe a few bytes"). But for a
, its small-string buffer is located at a randomized stack address — somewhere in the stack frame of main
, wherever that is! So (Godbolt):
#include <string>
int main() {
constexpr std::string a = "a";
static constexpr std::string c = "a";
constexpr const char *pa = a.data(); // invalid
constexpr const char *pc = c.data(); // OK
}
a.data()
can't be used as a constexpr variable's value, because we can't come up with a constant value for it that would also still be usable at runtime.
And since, on libstdc++, a
stores a copy of that pointer inside itself, therefore a
isn't valid as a constexpr variable on libstdc++ either. You need to use an implementation of std::string
that doesn't store a pointer-into-self inside itself, if you want that kind of thing to work.
See also:
- "Just how constexpr is
std::string
?" (2023-09-08) - "
constexpr std::string
update" (2023-10-13)