Date: Tue, 16 Dec 2025 20:39:59 -0500
N5032 Section 17.6.5 [ptr.launder] Paragraphs 2 and 3:
> Preconditions: p represents the address A of a byte in memory. An object X
> that is within its lifetime (6.8.4) and whose type is similar (7.3.6) to T is
> located at the address A. All bytes of storage that would be reachable through
> (6.9.4) the result are reachable through p.
>
> Returns: A value of type T* that points to X.
This section seems to assume that at most one possible X can exist. However, it
appears possible to construct two objects in a way that they could both be that
X:
struct S{
unsigned char x[4];
};
static_assert(sizeof(S)==4);
S s;
S*p=::new((void*)s.x)S;
std::launder((S*)s.x)->~S();
s.~S();
All pointers involved here can reach all 4 bytes of s, so reachability doesn't
affect anything. s is an object of type S which exists at some address A. An
array s.x spanning all of the bytes of s exists as a subobject of s. s.x must
have the same address as s because it occupies the same storage, and because of
pointer-interconvertibility. Then, another object of type S is created in s.x
where s.x provides storage for it. This new object *p occupies the same storage
as s.x, so it must have the same address as s.x and s. So there are two objects
s and *p which both have the same address, same type, same reachable bytes, and
are both within their lifetime.
Given this, it is unclear what std::launder((S*)s.x) points to. (S*)s.x points
to the same address as both s and *p, and both objects meet the requirements to
be X. Here is an example that demonstrates each case individually:
//same S as before
alignas(S)unsigned char a[4];
::new((void*)a)S;
std::launder((S*)a)->~S();
S b;
std::launder((S*)b.x)->~S();
std::launder((S*)s.x) could be either case, so is s.~S() defined (launder
results in p) or undefined (launder results in &s)?
See also GCC bug 121180, this issue can arise from types such as std::any which
store objects internally if they are small enough. Specifically, an empty class
can be stored in a std::any object and the std::any object can overlap with
another object that has the same empty class type just right to cause both empty
classes to have the same address.
I'm thinking about making a LWG issue for this. Though I don't think any
implementations implement these incorrectly because this overlapping requires
the storage to be unused and cannot happen in constant expressions.
> Preconditions: p represents the address A of a byte in memory. An object X
> that is within its lifetime (6.8.4) and whose type is similar (7.3.6) to T is
> located at the address A. All bytes of storage that would be reachable through
> (6.9.4) the result are reachable through p.
>
> Returns: A value of type T* that points to X.
This section seems to assume that at most one possible X can exist. However, it
appears possible to construct two objects in a way that they could both be that
X:
struct S{
unsigned char x[4];
};
static_assert(sizeof(S)==4);
S s;
S*p=::new((void*)s.x)S;
std::launder((S*)s.x)->~S();
s.~S();
All pointers involved here can reach all 4 bytes of s, so reachability doesn't
affect anything. s is an object of type S which exists at some address A. An
array s.x spanning all of the bytes of s exists as a subobject of s. s.x must
have the same address as s because it occupies the same storage, and because of
pointer-interconvertibility. Then, another object of type S is created in s.x
where s.x provides storage for it. This new object *p occupies the same storage
as s.x, so it must have the same address as s.x and s. So there are two objects
s and *p which both have the same address, same type, same reachable bytes, and
are both within their lifetime.
Given this, it is unclear what std::launder((S*)s.x) points to. (S*)s.x points
to the same address as both s and *p, and both objects meet the requirements to
be X. Here is an example that demonstrates each case individually:
//same S as before
alignas(S)unsigned char a[4];
::new((void*)a)S;
std::launder((S*)a)->~S();
S b;
std::launder((S*)b.x)->~S();
std::launder((S*)s.x) could be either case, so is s.~S() defined (launder
results in p) or undefined (launder results in &s)?
See also GCC bug 121180, this issue can arise from types such as std::any which
store objects internally if they are small enough. Specifically, an empty class
can be stored in a std::any object and the std::any object can overlap with
another object that has the same empty class type just right to cause both empty
classes to have the same address.
I'm thinking about making a LWG issue for this. Though I don't think any
implementations implement these incorrectly because this overlapping requires
the storage to be unused and cannot happen in constant expressions.
Received on 2025-12-17 01:42:27
