1

In this link, Storage reuse section shows the following example.

void x()
{
    long long n; // automatic, trivial
    new (&n) double(3.14); // reuse with a different type okay
} // okay

and here, one of the answers contains this code.

void f(float* buffer, std::size_t buffer_size_in_bytes)
{
    double* d = new (buffer)double[buffer_size_in_bytes / sizeof(double)];

    // we have started the lifetime of the doubles.
    // "d" is a new pointer pointing to the first double object in the array.        
    // now you can use "d" as a double buffer for your calculations
    // you are not allowed to access any object through the "buffer" pointer anymore since the // floats are "destroyed"       
    d[0] = 1.;
    // do some work here on/with the doubles...

    // ...
}

All the above are trying to perform placement new another type on the existing object, and the second code is manipulating the new object.

But, the first link is saying the following

If a new object is created at the address that was occupied by another object, then all pointers, references, and the name of the original object will automatically refer to the new object and, once the lifetime of the new object begins, can be used to manipulate the new object, but only if the original object is transparently replaceable by the new object.

Object x is transparently replaceable by object y if:

  • the storage for y exactly overlays the storage location which x occupied
  • y is of the same type as x (ignoring the top-level cv-qualifiers)

According to the emphasized sentence, the second example is undefined behavior because the new object type is not the same as the old one, and d[0] = 1.; manipulates the new object.
But, no one stated this problem in the comments.. so I'm confused.

  1. Is the first example defined behavior because it just performs placement new?
  2. Is the second example wrong because of UB?

Am I misunderstanding something..?

Eunho Choi
  • 167
  • 6
  • 2
    Note that all of the examples assume that the alignments and sizes of the types/allocations are suitable for the newly created objects. Otherwise that is UB. In particular the second example is suspicious since usually `alignof(double) > alignof(float)`. – user17732522 Mar 03 '22 at 20:10

1 Answers1

2

Both examples are legal.

"Transparent replaceability" only matters when you want to use a pointer to the old object to access the new object without std::launder.

But you're not doing that. You're manipulating the new object using the pointer returned by placement-new (not a pointer to the old object), which never needs std::launder.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • omg.. I think I read the paragraph roughly.. long sentence and many commas there.. haha thank you! – Eunho Choi Mar 03 '22 at 19:49
  • As an additional question, the standard says "If that portion of the array previously provided storage for another object, the lifetime of that object ends because its storage was reused, however the lifetime of the array itself does not end". does this mean that it is legal to placement new int on the first float object of float[]? – Eunho Choi Mar 03 '22 at 20:31
  • 1
    @EunhoChoi It's legal in any case. The question if it's legal to read the remaining floats after, and the quote seems to say it is. – HolyBlackCat Mar 03 '22 at 20:32
  • you are saying that it is also legal to access the new int object in the float array? – Eunho Choi Mar 03 '22 at 20:36
  • 1
    @EunhoChoi Depends on how you access it. If you use the pointer returned by placement-new, you're fine. If you're `reinterpret_cast`ing the pointer to array, you need `std::launder`. – HolyBlackCat Mar 03 '22 at 20:41
  • 2
    @EunhoChoi The sentence you are quoting doesn't apply to `float` arrays. It applies only to `unsigned char` and `std::byte` arrays. A `float` array cannot _provide storage_ (https://eel.is/c++draft/basic#intro.object-3) for other objects and therefore I would say the lifetime of the array will end when you place a new object of a different type into it. – user17732522 Mar 04 '22 at 05:37
  • @user17732522 I understand. so, it is impossible to exist T object inside `U` array object because the lifetime of `U` array object would end as soon as the lifetime of new `T` object begins, right? (except for the case when the array object is `unsigned char`, `std::byte`.. btw, why there is no `char`?) – Eunho Choi Mar 04 '22 at 07:45
  • @user17732522 If I'm right, contrary to HolyBlackCat said, it is illegal to "read the remaining floats". Pointer arithmetic on an storage in which the actual array object does not exist causes UB. – Eunho Choi Mar 04 '22 at 08:00
  • 1
    @EunhoChoi I think it will end the lifetime, but also I think pointer arithmetic on pointers to the array is still supposed to be allowed (but not accessing the `float` objects). Don't take this as definitive statement though, these are pretty unusual situations. As for why `char` isn't included, the [unresolved CWG issue 2489](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2489) is asking the same. – user17732522 Mar 04 '22 at 11:00