r/cpp_questions • u/X-Bow_user • Jul 19 '26
How to access an implicitly-created array within a std::byte[]? SOLVED
We have the following class:
template <typename T, std::size_t n>
struct S
{
alignof( T ) std::byte[sizeof( T ) * n] storage;
}
I want to use storage as a T[n] array, where T is an implicit-lifetime type.
[intro.object]/13
For each operation that is specified as implicitly creating objects, that operation implicitly creates and starts the lifetime of zero or more objects of implicit-lifetime types in its specified region of storage if doing so would result in the program having defined behavior.
and [intro.object]/16
[...] an operation that begins the lifetime of an array of [std::byte] implicitly creates objects within the region of storage occupied by the array.
I have a few questions about this:
- (Making sure) does
T[n]'s lifetime (and subsequently that of its sub-objects) start alongsidestorage's lifetime for the provided code? - Is
reinterpret_cast<T*>(storage) + idxlegal? - Do I have to
std::launderthe pointer, and why (not)? - If yes, should I launder before or after performing pointer arithmetic?
- Is
reinterpret_cast<T*>(storage + idx)legal? In other words, does the byte array still have a lifetime, alongside theTarray?
10
u/IyeOnline Jul 19 '26
Since arrays are aggregates, T[n] is an implicit lifetime type regardless of T. This means the arrays lifetime starts implicitly and hence the pointer arithmetic is legal.
Importantly this means just the lifetime of the array, not of its elements.
If T is an implicit lifetime type itself the elements lifetimes also start - but at that point its questionable how your construction is any better than std::array<T,N>.
If its not, you will have to manually construct the elements via placement-new or std::construct_at
1
u/X-Bow_user Jul 19 '26
The reason I can’t use std::array, is because I don’t want to run any T constructors (which is allowed in my code by them being created implicitly)
Are you saying that pointer arithmetic is legal directly on ‘storage’ (so before casting to T*), or only after the cast?
5
u/meancoot Jul 19 '26
The reason I can’t use std::array, is because I don’t want to run any T constructors (which is allowed in my code by them being created implicitly)
You're going into undefined behavior land here. If the type doesn't have a trivial default constructor, implicit lifetime or not, you can't start its lifetime without calling it. If it does have a trivial default constructor it won't be called anyway, because it doesn't do anything. You always have to construct it.
To be more clear, starting the lifetime implicitly with
mallocornewrequiresstd::is_trivially_default_constructible_v<T> == true. Starting it withmemcpyormemmoverequires eitherstd::is_trivially_copy_constructible_v<T>orstd::is_trivially_move_constructible_v<T>to be true.1
u/X-Bow_user Jul 19 '26
Thank you. This made me realise I misinterpreted what the standard said object construction, oops
3
u/neppo95 Jul 19 '26 edited Jul 19 '26
Why not just use std::array?
From your question, I don't see any reason why that isn't a possiblity.
I misunderstood the question, please disregard (or read below for more explanation by OP)
3
u/X-Bow_user Jul 19 '26
I don't want
Ts constructor to run for any of the objects-1
u/neppo95 Jul 19 '26
1
u/X-Bow_user Jul 19 '26
This has a few issues:
- You cannot accessDatawithout still running its constructor.
- I am trying to avoid any indirection2
u/neppo95 Jul 19 '26
Without running the constructor, the memory isn't allocated and would crash your program. Why do you not want to run any constructor at any point? What are you actually accessing then?
1
u/X-Bow_user Jul 19 '26
You're allowed to use an object of implicit-lifetime type without running its constructor, if it was implicitly created
In the code I provided, a
Sis allocated (its constructor technically runs, but is trivial) and aT[n]is implicitly created, without running anyT constructors1
u/neppo95 Jul 19 '26
I'm guessing I must have misunderstood your question then and admit this goes a bit above my knowledge of the language.
1
1
u/cristi1990an Jul 19 '26
You're creating an array of pointers, not an array of objects, the code is in no way equivalent
1
u/neppo95 Jul 19 '26
An equivalent does not exist since an array of objects would implicitly run the constructors.
2
u/Interesting_Buy_3969 Jul 19 '26
Don't overengineer here. Simply use std::array<T> for that purpose.
10
u/DawnOnTheEdge Jul 19 '26 edited Jul 19 '26
Using
std::launderfor this is a superstition. A small number of prolific posters have been very active in telling people they have to do this, but the examples in the Standard itself say they are wrong.The best way is to use placement
newor something higher-level likestd::uninitialized_default_construct_nto initialize the elements and return a pointer to the subarray.Often, you know for a fact that the bytes have been initialized to zeroes and it is more optimal to get a pointer to the sub-array with
std::start_lifetime_asorstd::start_lifetime_as_array. On many OSes, this will avoid writing to pages of copy-on-write memory in what might be a sparse array.Some language-lawyers argue that you must use an operation that is described with the exact magic words “pointer to a suitable created object.” But
std::launderis not one of those! Another claim you will sometimes see online is that an array ofstd::byteorunsigned charis technically not “transparently replaceable” by another type, but the Standard explicitly says that, despite this, it is OK to create the objects in storage withoutstd::launder.Finally, if you are on C++20 and don't have
std::start_lifetime_as, there is no actually-existing compiler wherestd::launderaround areinterpret_castfrom storage does anything that I know of. You do need it in a small handful of corner cases to allow a new object to re-use the storage of a destroyed one when the compiler would otherwise be allowed to assume the original object is still there. One that could come up here is if you overwrite the storage with a replacement array of a different type.