The C++ 20 draft [concept.default.init] does not precisely define default_initializable
template<class T>
concept default_initializable = constructible_from<T> &&
requires { T{}; } &&
is-default-initializable <T>; // exposition-only
and describe what is-default-initializable should do with the following words:
For a type
T
, is-default-initializable <T> is true if and only if the variable definition
T t;
is well-formed for some invented variable t; otherwise it is false. Access checking is performed as if in a context unrelated to T. Only the validity of the immediate context of the variable initialization is considered.
On cppreference we find the following suggestion for a possible implementation:
template<class T>
concept default_initializable =
std::constructible_from<T> &&
requires { T{}; } &&
requires { ::new (static_cast<void*>(nullptr)) T; };
The placement-new operator invoked with a nullptr
argument results in undefined behavior.
9) Called by the standard single-object placement new expression. The standard library implementation performs no action and returns ptr unmodified. The behavior is undefined if this function is called through a placement new expression and ptr is a null pointer.
My question is now: is the suggested possible implementation actually valid? On the one hand I think no, because an expression is involved which exhibits undefined behavior. On the other hand I think yes, because this expression occurs in unevaluated context and therefore may not need to have well-defined behavior (?) and just needs to be syntactically valid. But I cannot find clear evidence for one or the other.
Second question: if the latter turns out to be true, then why does this placement-new construction satisfy the standard's requirement that T t;
must be well-formed? To me it looks like a strange hack, because neither simple nor compound requirements offer the possibility to require T t;
exactly. But why does this work?
When specified, undefined behavior are the consequence of an evaluation [expr.new]/20
If the allocation function is a non-allocating form ([new.delete.placement]) that returns null, the behavior is undefined.
[expr.prim.req]/2:
Expressions appearing within a requirement-body are unevaluated operands.
[expr.prop]/1:
An unevaluated operand is not evaluated.
So there are no undefined behavior has it requires a return value of the placement new allocation function, but such value is not computed.
If it were not the case such common construct decltype( std::declval<int&>() + std::declval <int&> ())
would be UB too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With