7

One of the preconditions on std::launder requires that object is within its lifetime. I assume that it is a necessary condition for being able to dereference the element. Does it mean, that if I obtain a pointer to to an array element, then no launder is needed, if I perform only arithmetic? Or it is just UB?

A clarifying code example. Some remarks clarifying the question are made within the comments to the code.

alignas(T) std::byte memory[3 * sizeof(T)]; // implicitly creates an array of 3 T 
                                            // elements on the stack
auto arr_ptr = std::launder(reinterpret_cast<T(*)[3]>(memory));

// Now we have an implictly created array of 3 T, but unless T is an 
// implicit-lifetime type, no element has began its lifetime yet
auto ptr1 = reinterpet_cast<T*>(memory); // points to the storage occupied by the 
                                         // first element of T
// is arithmetic on ptr1 valid? Or it is treated as regular conversion between
// types that are not pointer interconvertible, and since no T element exists
// there, arithmetic is UB by the standard.
// Or, since the address is of an valid element of array of T 
// (storage address is the same, and we do not // dereference it), everything is 
// okay?
auto ptr2 = std::launder(reinterpret_cast<T*>(memory));
// does not point to an object within its lifetime, UB, therefore, it can not be 
// used for pointer arithmetic.
  • 2
    `alignas(T) std::byte memory[3 * sizeof(T)]` does not *implicitly creates an array of 3 T elements on the stack*. All it does allocate enough space for such an array. You need to use placement to construct the array on top of the memory you acquired. – NathanOliver Mar 24 '21 at 15:19
  • @NathanOliver Does the `reinterpret_cast` does though? I think some rules have changed in C++20 – Guillaume Racicot Mar 24 '21 at 15:22
  • @DrewDormann placement new with an array is a bad idea, it may lead to straight up corruption of memory – Guillaume Racicot Mar 24 '21 at 15:22
  • 1
    @GuillaumeRacicot AFAIK, no, C++20 didn't open up `reinterpret_cast` for that. `std::byte_cast` was added to convert bytes of one type to another distinct type. – NathanOliver Mar 24 '21 at 15:23
  • @NathanOliver thanks for the clarification. I wonder why `std::start_lifetime_as` haven't passed either though. – Guillaume Racicot Mar 24 '21 at 15:25
  • 3
    @NathanOliver It does by c++20, I believe. See https://en.cppreference.com/w/cpp/language/object#Object_creation and https://eel.is/c++draft/intro.object#10 and http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html#when-to-create-objects. – Myrddin Krustowski Mar 24 '21 at 15:25
  • Hmm, looks like arrays get a pass, even if the element types don't. TIL :) – NathanOliver Mar 24 '21 at 15:28
  • @RazielMagius The idea of an array starting with dead objects scares me, but it seems necessary here... – ph3rin Mar 24 '21 at 15:33
  • @eerorika `reinterpret_cast` does not. The beginning of lifetime of `std::byte` array does, implicitly, I believe. `reinterpret_cast` only "clarifies" that T[3] is the implicit-lifetime object that was created by `alignas(T) std::byte memory[3 * sizeof(T)]` – Myrddin Krustowski Mar 24 '21 at 15:36
  • _is arithmetic on `ptr1` valid?_ It would violate [expr.add]/6, if `T` is not «_cv_ `std::byte`». – Language Lawyer Mar 24 '21 at 20:01
  • I think that you are right. So basically, the only way to obtain access to array elements if they do not within their lifetime is through the array itself? (in our case by `*arr_ptr`). – Myrddin Krustowski Mar 24 '21 at 21:49
  • If you want me to be notified, you needa mention me. _So basically, the only way to obtain access to array elements if they do not within their lifetime is through the array itself?_ `auto ptr1 = (T*)::new (&memory) std::byte[3 * sizeof(T)];` should work, if such placement new can produce a pointer to the first array element of the implicitly-created `T[n≤3]`. – Language Lawyer Mar 25 '21 at 00:28
  • 1
    @LanguageLawyer *if such placement new can produce a pointer to the first array element of the implicitly-created T[n≤3]* - But can it? I think the conflict is that `ptr1`'s pointer value is pointing to a `std::byte`, and thus `std::launder` is required to obtain a pointer to `T`, however, that first array element isn't alive yet, so `std::launder` won't work. – ph3rin Mar 29 '21 at 15:29
  • @Meowmere you're right, _new-expression_ is not described as producing a pointer to an implicitly-created object. Explicit invocation of non-allocating `operator new`/`operator new[]` have to be used. – Language Lawyer Mar 29 '21 at 15:39
  • It looks like the conflict here is that we cannot obtain a `T*` (which we could do pointer arithmetic with) without obtaining a pointer to the array of `T`, but the latter is only obtainable when the array has a fixed size, making and unusable for dynamic containers. The workaround (not applicable in c++20) would be to use `allocator_traits::allocate` which magically returns a pointer to the first element of the array (a dead object). – ph3rin Apr 08 '21 at 16:36
  • @Meowmere It seems that it is applicable in C++20. https://en.cppreference.com/w/cpp/memory/allocator/allocate - states that return result is "Pointer to the first element of an array of n objects of type T whose elements have not been constructed yet". So it is applicable to c++20 as well. Am I missing something? – Yuval K Sep 10 '21 at 08:07
  • Does this answer your question? [Is pointer arithmetic on allocated storage allowed since C++20?](https://stackoverflow.com/questions/60631224/is-pointer-arithmetic-on-allocated-storage-allowed-since-c20) – Yuval K Sep 13 '21 at 14:38
  • [Is pointer arithmetic on allocated storage allowed since C++20?](https://stackoverflow.com/questions/60631224/is-pointer-arithmetic-on-allocated-storage-allowed-since-c20). If the answer can be edited to make it more explicit and clear, it answers this question as well, I believe. – Yuval K Sep 13 '21 at 14:46

0 Answers0