Is it legal to write something like this?
#include <memory>
struct A {
int i, j;
constexpr A() : i((std::construct_at(&j, 2), j-1)) {}
};
constexpr A a{};
static_assert(a.i == 1);
static_assert(a.j == 2);
Here, i
-member initializer first initializes j
member using std::construct_at
, then reads its value in j-1
.
On practice I see that all GCC, MSVC and Clang accept the program. Online demo: https://gcc.godbolt.org/z/YzEoPPj96
But Clang issues the warning:
<source>:5:50: warning: field 'j' is uninitialized when used here [-Wuninitialized]
5 | constexpr A() : i((std::construct_at(&j, 2), j-1)) {}
| ^
This looks contradicting, since reading of uninitialized values in constant expressions must result in hard fail. Is the program well formed, and the diagnostic is simply wrong?
And thanks to @TedLyngmo, here is a more complicated example with heap allocations:
#include <string>
struct A {
std::string i, j;
constexpr A()
: i(((void)std::construct_at(&j,
"Hello world, this is very funny indeed "
"and this is a long string"),
j + " with some exta in it"))
, j([k=std::move(j)]()mutable { return std::move(k); }()) {}
};
static_assert( A{}.i.length() == 85 );
static_assert( A{}.j.length() == 64 );
Online demo: https://gcc.godbolt.org/z/zcb4hbhY3
According to [specialized.construct], std::construct_at(&j, 2)
is ±equivalent to ::new (&j) int(2)
.
this->j
's containing object (a
) is not within lifetime, so the newly-created object is not a
's subobject, so it reuses a
's storage without being nested within it. I'd say this currently is «implicit UB», so it can be claimed that «An expression E is a core constant expression unless the evaluation of E … would evaluate one of the following: … an operation that would have undefined behavior as specified in [intro] through [cpp]» does not apply.
CWG2757 aims to make such UB explicit. At least, its intent is clear. Although, its Proposed resolution (reviewed by CWG 2023-10-20) wrongly claims that creating an object nested within some object o does not reuse the storage of o, whilst it just does not end its lifetime. Creating an object always reuses the storage if it was used at the time of creating.