Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is UB in unevaluated context (e.g. requires-expressions) still UB?

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?

like image 867
UniversE Avatar asked May 25 '20 10:05

UniversE


1 Answers

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.

like image 100
Oliv Avatar answered Nov 11 '22 17:11

Oliv