Home > Enterprise >  Does implicit object creation apply in constant expressions?
Does implicit object creation apply in constant expressions?

Time:01-03

#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 chars.

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 chars.

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.

  • Related