I'm trying to change the active member of a constexpr
union using construct_at
and get the following error when constructor initializes it's member using initializer list vs. member. Can someone explain why?
#include <memory>
struct Z {
#if 1 // If this changes to zero it does not compile
constexpr Z(int x) : y(x){
}
#else
constexpr Z(int x) {
y = x;
}
#endif
int y;
};
struct W {
constexpr W(int x) {
y = x;
}
W(const W&) {}
int y;
};
union U {
Z z;
W w;
constexpr U(int z) : w(z) {
}
};
constexpr int func() {
constexpr U u(10);
std::construct_at(&u.z, 10);
// ::new (&u.z) Z(10);
return u.z.y;
}
int main() {
static_assert(func() == 1);
}
Error:
source>: In function 'int main()':
<source>:37:26: error: non-constant condition for static assertion
37 | static_assert(func() == 10);
| ~~~~~~~^~~~~
<source>:37:23: in 'constexpr' expansion of 'func()'
<source>:31:21: in 'constexpr' expansion of 'std::construct_at<const Z, int>((& u.U::z), 10)'
/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/stl_construct.h:97:14: in 'constexpr' expansion of '((Z*)<anonymous>)->Z::Z(<anonymous>)'
<source>:8:12: error: modifying a const object '((Z*)this)->Z::y' is not allowed in a constant expression
8 | y = x;
| ~~^~~
<source>:30:16: note: originally declared 'const' here
30 | constexpr U u(10);
| ^
Compiler returned: 1
While I don't know what exactly the case with working/non-working ctors, you have a constexpr U u(10);
on which you later try to call a modifying function (ctor) by calling std::construct_at(&u.z, 10);
. What do you expect from trying to modify a constexpr
object? Remove constexpr
on the u
object, it won't make your function less constexpr
.
As noted in the other answer, constexpr
on u
is wrong here. But I will try to say something about the compiler behavior with your code.
std::construct_at
will construct a new object at the given storage location. However constexpr
implies const
, meaning you are trying to create a new object in a const complete object with automatic storage duration. That is not allowed and causes undefined behavior. (The call to std::construct_at
is however not ill-formed. It may be called with a const
pointer.)
Since this is happening during the evaluation of an expression that is required to be a constant expression, the question is then whether this makes the expression not a constant expression, i.e. whether the compiler has to diagnose the undefined behavior.
Generally any core language undefined behavior must be diagnosed by the compiler. Any standard library undefined behavior may be diagnosed by the compiler.
So the question would be here as which the std::construct_at
call counts. The function itself is specified as equivalent to the corresponding placement-new ([specialized.construct]/2), except that a special exception is made that std::construct_at
is allowed in a constant expression unless "the underlying constructor call disqualifies [the expression] from being a core constant expression" ([expr.const]/6.1).
Although I don't think it is really clear, I would guess that this is supposed to count as core language undefined behavior since it is not violating any precondition or stated undefined behavior in the library clauses, but rather one stated in the core language clauses.
With that the expression func() == 1
would not be a constant expression in either of the two versions of the code and the compiler should put out a diagnostic such as GCC is doing in at least one of the shown cases.
However, as I hinted above, I am not confident that my interpretation of the constant expression requirements is the intended one here.
I guess [expr.const]/6.1 could also be read such that the undefined behavior requirement does not apply to a std::construct_at
call at all, but only to the underlying constructor call. But that does really seem unintended.
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