#include <memory>
int main() {
constexpr auto v = [] {
std::allocator<char> a;
auto x = a.allocate(10);
x[2] = 1;
auto r = x[2];
a.deallocate(x, 10);
return r;
}();
return v;
}
Is the program ill-formed? Clang thinks so, GCC and MSVC don't: https://godbolt.org/z/o3bcbxKWz
Removing the constexpr
I think the program is not ill-formed and has well-defined behavior:
By [allocator.members]/5 the call a.allocate(10)
starts the lifetime of the char[10]
array it allocates storage for.
According to [intro.object]/13 starting the lifetime of an array of type char
implicitly creates objects in its storage.
Scalar types such as char
are implicit lifetime types. ([basic.types.general]/9
[intro.object]/10 then says that objects of type char
are created in the storage of the char[10]
array (and their lifetime started) if that can give the program defined behavior.
Without beginning the lifetime of the char
object at x[2]
, the program without constexpr
would have undefined behavior due to the write to x[2]
outside its lifetime, but the char
object can be implicitly created due to the arguments above, making the program behavior well-defined to exit with status 1
.
With constexpr
, I am wondering if the program is ill-formed or not. Does implicit object creation apply in constant expressions?
According to [intro.object]/10 objects are implicitly created to give the program defined behavior, but does being ill-formed count as defined behavior?
If not, then the program should not be ill-formed because of implicit creation of the char
object for x[2]
.
If yes, then the next question would be if it is unspecified whether the program is ill-formed or not, because [intro.object]/10 also says that it is unspecified which objects are implicitly created if multiple sets can give the program defined behavior.
From a language design perspective I would expect that implicit object creation is not supposed to happen in constant expressions, because verifying the (non-)existence of a set of objects making the constant expression valid is probably infeasible for a compiler in general.
CodePudding user response:
2469. Implicit object creation vs constant expressions
It is not intended that implicit object creation, as described in 6.7.2 [intro.object] paragraph 10, should occur during constant expression evaluation, but there is currently no wording prohibiting it.
CodePudding user response:
Clang is incorrect here. You've already cited all the parts in the spec that make it well-formed. std::allocator<T>::allocate
is constexpr
; you get a pointer to char*
; allocator<T>::allocate
creates an array of T
; creating an array of char
implicitly creates objects; accessing a char
attempts to cause UB, but IOC prevents UB, so UB doesn't happen. Therefore: the code isn't allowed to be il-formed.
Clang claims full support for both IOC and constexpr allocators, so this code should work.
Does implicit object creation apply in constant expressions?
All expressions are core constant expressions unless [expr.const]/5 explicitly excludes it. Nothing there mentions operations which might be UB that determines which objects are created, so such operations must be included.
IOC prevents an expression from being UB.
I would expect that implicit object creation is not supposed to happen in constant expressions, because verifying the (non-)existence of a set of objects making the constant expression valid is probably infeasible for a compiler in general.
You have forgotten about the other restrictions on constexpr code. So long as [expr.const]/5 continues to explicitly forbid reinterpret_cast
and conversions from void*
, the number of ways you can abuse IOC is pretty limited. You cannot, for example, take the pointer returned by your allocate(10)
call and convert it to an int*
. So the compiler knows that the only objects that can be implicitly created in that storage are char
s.
So at constant evaluation time, the compiler could just take the result of allocator<char>::allocate
and create all the char
members of that array immediately before returning it. There's no constexpr-valid way for you to take that storage and implicitly create anything other than char
s.
And using allocator<T>::allocate
when T
isn't a byte-wise type will not implicitly create objects in that storage. So either you're just getting a pointer to an array of unformed elements, or you're getting a pointer to an array of byte-wise types.
I'd guess Clang forgot to check this particular case.