Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using std::launder to get a pointer to an active union member from a pointer to an inactive union member?

Consider this union:

union A{
  int a;
  struct{
    int b;
    } c;
  };

c and a are not layout-compatibles types so it is not possible to read the value of b through a:

A x;
x.c.b=10;
x.a+x.a; //undefined behaviour (UB)

For Trial 1 and Trial 2 see this question

Trial 3

Now let's use std::launder for what it does not seems to be intended:

A x;
x.a=10;
auto p = &x.a;                 //(1)
x.c.b=12;                      //(2)
p = std::launder(p);           //(2')
*p+*p;                         //(3)  UB?

Could std::launder change anything? According to [ptr.launder]:

template <class T> constexpr T* launder(T* p) noexcept;

Requires: p represents the address A of a byte in memory. An object X that is within its lifetime and whose type is similar to T is located at the address A. All bytes of storage that would be reachable through the result are reachable through p (see below).

Returns: A value of type T * that points to X.

Remarks: An invocation of this function may be used in a core constant expression whenever the value of its argument may be used in a core constant expression. A byte of storage is reachable through a pointer value that points to an object Y if it is within the storage occupied by Y, an object that is pointer-interconvertible with Y, or the immediately-enclosing array object if Y is an array element. The program is ill-formed if T is a function type or cv void.

The bolded sentence emphasizes a someting that troubles me. If p is an invalid pointer value, how could any byte of storage be accessible? On the other hand with such a reading std::launder is just not usable.

Otherwise, could p's value at (2) be a pointer value that represents a region of storage as is talked about in a "Note" in [basic.life]:

If these conditions are not met, a pointer to the new object can be obtained from a pointer that represents the address of its storage by calling std​::​launder ([support.dynamic]).

like image 612
Oliv Avatar asked May 25 '19 19:05

Oliv


1 Answers

It is explicitly allowed in a note.

basic.life contains the following rule that makes std::launder unnecessary:

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
  • neither the original object nor the new object is a potentially-overlapping subobject.

[ Note: If these conditions are not met, a pointer to the new object can be obtained from a pointer that represents the address of its storage by calling std::launder. — end note ]

This "new object is created at the storage location which the original object occupied" case clearly applies here because:

An object is created ... when implicitly changing the active member of a union...

All the bullet conditions are met, as "potentially overlapping subobject" refers to base class subobjects, which union members are not. (And in the version you linked to, that bullet mentioned base class subobjects directly.)

However even if this interpretation were to change against unions, the note specifically mentions that std::launder bypasses this restriction.

Do note that older versions of the Standard excluded subobjects from this rule... but the note makes it clear that std::launder would have bypassed that problem as well.

like image 72
Ben Voigt Avatar answered Oct 18 '22 09:10

Ben Voigt