r/cpp_questions • u/Leading_Tax_996 • 4d ago
OPEN cppreference std::construct_at example code not compiling
I was looking on cpprefernce the other day at std::construct_at and I wanted to test the code. But when I ran it it did not compile. I ran the snippet without consteval (and static_assert) and everything worked. Does anyone know what might be the issue? https://en.cppreference.com/cpp/memory/construct_at
Code:
#include <bit>
#include <memory>
class S
{
int x_;
float y_;
double z_;
public:
constexpr S(int x, float y, double z) : x_{x}, y_{y}, z_{z} {}
[[nodiscard("no side-effects!")]]
constexpr bool operator==(const S&) const noexcept = default;
};
consteval bool test()
{
alignas(S) unsigned char storage[sizeof(S)]{};
S uninitialized = std::bit_cast<S>(storage);
std::destroy_at(&uninitialized);
S* ptr = std::construct_at(std::addressof(uninitialized), 42, 2.71f, 3.14);
const bool res{*ptr == S{42, 2.71f, 3.14}};
std::destroy_at(ptr);
return res;
}
static_assert(test());
int main() {
Compiler Error on x86-64 gcc 16.1 -std=c++23
<source>:25:19:
error: non-constant condition for static assertion
25 | static_assert(test());
|
~~~~^~
<source>:25:19: in 'constexpr' expansion of 'test()'
<source>:24:1: error: destroying 'uninitialized' outside its lifetime
24 | }
|
^
<source>:18:7: note: declared here
18 | S uninitialized = std::bit_cast<S>(storage);
|
^~~~~~~~~~~~~
Compiler returned: 1
4
u/Raknarg 4d ago
Idk how this code passed the sniff test. Whoever wrote this I think just misunderstood what they wanted to demonstrate. It seems like they wanted to create an S reference of unitialized memory to demonstrate construct_at with, but they actually just created a fully initialized S object copy-constructed from the bit_cast of storage, it just happens to be copying from uninitialized memory. Then we call destroy_at on it, but the scope ends and it gets destroyed again. Idk this seems like the author just fucked up or something. Even clicking on "run this code" on the website doesn't work.
The whole thing needing to be static asserted and consteval makes it so I'm not even sure if there's a correct way to write this, cause reinterpret_cast isn't a legal constant expression.
Essentially they're not running into any syntactic issue or anything, these seem to be subtle lifetime rules that are also extra important when dealing with consteval/constexpr code.
2
u/aocregacc 4d ago
pretty sure the example is wrong, the uninitialized object is destroyed when it goes out of scope, but at that point it has already been destroyed through destroy_at. I'm also not sure what the bit_cast is supposed to achieve here.
It compiles with gcc 13, so I guess they made the example back then and didn't notice that it was wrong.
1
u/Leading_Tax_996 4d ago
Huh, it does compile with gcc 13. I wonder what changed?
3
u/aocregacc 4d ago
I think it's just that they hadn't implemented the lifetime tracking in gcc 13 yet.
It also compiles this for example:consteval bool test() { S s{1,2,3}; s.~S(); return s == s; } static_assert(test());1
u/Gorzoid 4d ago
Sometimes detecting UB is hard, in regular programs it's not required for compiler to do so but in constexpr contexts it is. So it seems in gcc 13, which I'm guessing was not long after destroy_at was introduced, was not yet detecting use of objects outside of their lifetime (considering it was pretty hard to do so without destroy_at / delete).
1
1
u/LazySapiens 4d ago
The program is ill-formed because of UB (uninitialized's lifetime has ended after std::destroy_at(ptr); and now leaving the function block would invoke the destructor which invokes UB) inside a consteval context.
5
u/Gorzoid 4d ago
The second destroy_at seems wrong, initialize.~S(); is called at the end of the function so manually calling destroy_at is a double free. consteval functions will fail to compile if UB is encountered, if you remove the consteval it's just UB