1

std::launder example has this block of code:

int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0])); 
// Undefined behavior: x2[1] would be reachable through the resulting pointer to x2[0]
// but is not reachable from the source

What? Example from std::aligned_storage makes it seem to me like that's not the case:

std::aligned_storage_t<sizeof(T), alignof(T)> data[N];

// Access an object in aligned storage
const T& operator[](std::size_t pos) const 
{
    // Note: std::launder is needed after the change of object model in P0137R1
    return *std::launder(reinterpret_cast<const T*>(&data[pos]));
}

Here's where confusion comes in: &data[pos] is just &data[pos][0], because &p == &p[0] where p is an array.

How come this is okay if std::aligned_storage can hardly if at all be implemented in any other way but something like alignas(T) std::byte[sizeof (T)];? Does the array being in a struct somehow magically make it okay? Why?

Let's say I have

template <typename T>
using uninitialized = alignas(T) std::byte[sizeof (T)];

constexpr auto N = 5;
uninitialized<int> array[N];
for (std::size_t i = 0; i < N; i++) {
    new (&array[i]) int(i);
}

Now what? Isn't any cast like

auto laundered_value_ptr = std::launder(reinterpret_cast<int*>(&array[i]));

identical to the first example? What about this:

auto laundered_array_ptr = std::launder(reinterpret_cast<int*>(array));
laundered_array_ptr[0] = 9;
laundered_array_ptr[2] = 76;

If I follow, it seems like there's no way to use this memory correctly, because using a std::byte(*)[sizeof (int)] means that basically anything around it is reachable, and following first example, everything written in this question is may aswell be UB.

I compiled these examples using g++ -g -O0 -Wextra -Wall -Wpedantic -std=c++20 -fsanitize=undefined -fsanitize=address and curiously had gotten not even a warning which leaves me completely stumped.

I doubt this matters at all, but here's the version of compiler that I am using.

g++ (GCC) 12.1.1 20220730
yotsugi
  • 68
  • 1
  • 5
  • 1
    From the linked reference: "an object X is located at the address A" and "he type of X is the same as T" For the example shown that's not really correct. The type of "X" located at address "A" is `int[2][10]`, while the type of "T" is `int[10]`. – Some programmer dude Aug 16 '22 at 15:32
  • @Someprogrammerdude sure, it is, but why does the page go on a tangent about something incomprehensible that doesn't even have anything to do with this? – yotsugi Aug 16 '22 at 16:43
  • Probably because there's just no simple way to specify all of this. And the reference page is actually not comprehensive, the actual specification is even "worse" in that regard. – Some programmer dude Aug 16 '22 at 17:12

1 Answers1

1

You wrote:

&data[pos] is just &data[pos][0], because &p == &p[0] where p is an array.

In fact, that's not true. If p is an array (call its type T[N]), the expression &p == &p[0] will not compile. The left-hand side has type T(*)[N], and the right-hand side has type T*. These cannot be compared directly, any more than you could compare an int* with a char*.

However, static_cast<void*>(&p) == static_cast<void*>(&p[0]) will be true.

The two pointers represent the same address, but they are not substitutable for each other.

&data[pos] is a pointer to one element of data, and all other elements of data are reachable through it. On the other hand, (supposing that std::aligned_storage_t is an alias for an array of unsigned char) &data[pos][0] points only to a single element of an element of data; it does not point to the entire element of data. So all other elements of data[pos] are reachable through it, but other elements of data are not.

Since all elements of data are reachable through &data[pos], it follows that all bytes in data are reachable through &data[pos]. After this pointer is converted to const T*, the other T's that are stored inside the other elements of data are also reachable from the resulting pointer, but the bytes of those T's were also reachable from the original pointer, so the preconditions of std::launder are met.

If we used &data[pos][0] as the argument to reinterpret_cast, only the bytes in the single element data[pos] would be reachable from that pointer, and the preconditions would not be met.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • "The two pointers represent the same address, but they are not substitutable for each other.", thank you, my issue after all was a misunderstanding of arrays whose elements are other arrays, and this answer makes it perfectly clear. – yotsugi Aug 17 '22 at 07:47