6

I was reviewing a code I proposed to initialize a std::array at compile-time for non-default-constructible objects: https://stackoverflow.com/a/78676552/21691539

in the second version I'm doing:

// creating storage on std::byte to benefit from "implicit object creation"
alignas(alignof(T)) std::byte storage[sizeof(std::array<T, N>)];
std::array<T, N>* const Array =
    std::launder(reinterpret_cast<std::array<T, N>*>(storage));

// initializing objects in the storage
T* it =
    Array->data();  // construct objects in storage through std::construct_at
for (std::size_t i = 0; i != N; ++i) {
    std::construct_at(it, gen(static_cast<int>(i))); // UB?
    // new (it) T(gen(static_cast<int>(i))); // not UB?
    ++it;
}

LIVE

I wanted to rely on the fact that std::array is an implicit lifetime type but I'm thinking now that I'm UB for the following reason: Array has indeed started its lifetime but none of his subobjects of type T:

Some operations are described as implicitly creating objects within a specified region of storage.[...] [ Note: Such operations do not start the lifetimes of subobjects of such objects that are not themselves of implicit-lifetime types. — end note ]

https://timsong-cpp.github.io/cppwp/n4861/intro.object#10

Does std::construct_at starts this lifetime or just calls the constructor of an object that is already alived inplace (in which case my code would be UB)?

What made me have doubts is the example provided in cppreference that goes through a std::bit_cast to start an object lifetime (thus, AFAIU, in a different memory location than the provided storage).


further research: std::construct_at:

Effects: Equivalent to: return ::new (voidify(*location)) T(std::forward(args)...);

https://timsong-cpp.github.io/cppwp/n4861/specialized.construct#2

Objects creation 'emphasis mine':

An object is created by a definition, by a new-expression, by an operation that implicitly creates objects (see below), when implicitly changing the active member of a union, or when a temporary object is created.

https://timsong-cpp.github.io/cppwp/n4861/intro.object#1

Linking these two section, is it correct to say that std::construct_at, in fact, actually start an object lifetime at the given location?

18
  • 3
    timsong-cpp.github.io/cppwp/n4861/intro.object#10: AFAIU, subobjects lifetime is not started if they are not themselves of implicit lifetime types. Commented Oct 8, 2024 at 10:32
  • 1
    @Eljay there are C++ objects and there are Object oriented objects. They are not the same. Commented Oct 8, 2024 at 12:42
  • 2
    @DanielLangr, Pepijn Kramer • I was mistaken. I confused the inability to return an array with an array not being an object. At some point in time (decades ago) my brain associated arrays as "not an object" because of that. mea culpa, and thank you for the correction. Commented Oct 8, 2024 at 12:50
  • 1
    @RemyLebeau: It's an implicit lifetime type; it's already there. Commented Oct 8, 2024 at 18:15
  • 1
    @RemyLebeau Yes, std::array<T,N> is an implicit-lifetime type due to class.prop/9, independently of what T is. Commented Oct 9, 2024 at 8:04

2 Answers 2

4

According to [basic.life]/1, an object's lifetime starts immediately after its initialization is complete, including "vacuous initialization", which is the case where the initialization phase does nothing at all (such as default-initialization for an object of scalar type).

std::construct_at is specified to use the new operator, and new is specified to initialize the object created ([expr.new]/24), so the lifetime begins. Note that the new operator, similarly to an object definition, performs "ordinary" object creation that, unlike implicit object creation, is guaranteed to recursively initialize bases and members according to the usual rules (e.g., [class.base.init], [dcl.init.aggr]).

Sign up to request clarification or add additional context in comments.

3 Comments

"new operator` → "new expression". Please, do not confuse these two terms; new operator (operator new) is an allocation function.
@DanielLangr Operators are used to build up complex expressions from simpler ones. For example, a conditional expression x ? y : z is an expression that's built up by applying the conditional operator ?: to its three operands. By analogy, the keyword new that introduces a new-expression is also an operator. Unfortunately as you point out this is one of the cases where the operator (syntactic construct) doesn't do exactly what the corresponding operator function does.
You're right, the new expression is also listed here: en.cppreference.com/w/cpp/language/operator_precedence. Even a function call is operator according to this page. Thanks for clarification.
1

Really, the only thing more that could be linked to to show you that you are correct is [expr.new]/1:

The new-expression attempts to create an object of the type-id or new-type-id to which it is applied.

So yes, std::construct_at creates objects. If it required the object to exist, that would be stated in its requirements. And it'd be very weird, since calling a constructor on an existing object... isn't really a thing.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.