简体   繁体   中英

std::launder use cases in C++20

[1]

Are there any cases in which the addition of p0593r6 into C++20 ( § 6.7.2.11 Object model [intro.object] ) made std::launder not necessary, where the same use case in C++17 required std::launder , or are they completely orthogonal ?


[2]

The example in the spec for [ptr::launder] is:

struct X { int n; };
const X *p = new const X{3};
const int a = p->n;
new (const_cast<X*>(p)) const X{5}; // p does not point to new object ([basic.life]) because its type is const
const int b = p->n;                 // undefined behavior
const int c = std::launder(p)->n;   // OK

Another example is given by @Nicol Bolas in this SO answer , using a pointer that points to a valid storage but of a different type:

aligned_storage<sizeof(int), alignof(int)>::type data;
new(&data) int;
int *p = std::launder(reinterpret_cast<int*>(&data));

Are there other use cases, not related to allowing casting of two objects which are nottransparently replaceable , for using std::launder ?

Specifically:

  • Would reinterpret_cast from A* to B*, both are pointer-interconvertible , may require using std::launder in any case? (ie can two pointers be pointer-interconvertible and yet not be transparently replaceable ? the spec didn't relate between these two terms).
  • Does reinterpret_cast from void* to T* require using std::launder ?
  • Does the following code below require use of std::launder ? If so, under which case in the spec does it fall to require that?

A struct with reference member, inspired by this discussion :

struct A {
    constexpr A(int &x) : ref(x) {}
    int &ref;
};

int main() {
    int n1 = 1, n2 = 2;
    A a { n1 };
    a.~A();
    new (&a) A {n2};
    a.ref = 3; // do we need to launder somebody here?
    std::cout << a.ref << ' ' << n1 << ' ' << n2 << std::endl;
}

Before C++17, a pointer with a given address and type always pointed to an object of that type located at that address, provided that the code respects the rules of [basic.life]. (see: Is a pointer with the right address and type still always a valid pointer since C++17? ).

But in the C++17 standard added a new quality to a pointer value. This quality is not encode within the pointer type but qualifies directly the value, independently of the type (this is the case also of the traceability). It is described in [basic.compound]/3

Every value of pointer type is one of the following:

  • a pointer to an object or function (the pointer is said to point to the object or function), or

  • a pointer past the end of an object ([expr.add]), or

  • the null pointer value for that type, or an invalid pointer value.

This quality of a pointer value has its own semantic (transition rules), and for the case of reinterpret_cast it is described in the next paragraph:

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast.

In [basic-life], we can find an other rule that describes how transitions this quality when an object storage is reused:

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, [...]

As you can see the quality "pointer to an object" is attached to a specific object.

That means that in the variation bellow of the first example you give, the reinterpret_cast does not allow us not to use the pointer optimization barrier:

struct X { int n; };
const X *p = new const X{3};
const int a = p->n;
new (const_cast<X*>(p)) const X{5}; // p does not point to new object ([basic.life]) because its type is const
const int b = *reinterpret_cast <int*> (p);                 // undefined behavior
const int c = *std::launder(reinterpret_cast <int*> (p)); 

A reinterpret_cast is not a pointer optimization barrier: reinterpret_cast <int*>(p) points to the member of the destroyed object.

An other way to conceive it is that the "pointer to" quality is conserved by reinterpret_cast as long as the object are pointer inter-convertible or if its casted to void and then back to a pointer inter-convertible type. (See [exp.static_cast]/13 ). So reinterpret_cast <int*>(reinterpret_cast <void*>(p)) still points to the destroyed object.

For the last example you gives, the name a refers to a non const complete object, so the original a is transparently replaceable by the new object.


For the first question you ask: "Are there any cases in which the addition of p0593r6 into C++20 (§ 6.7.2.11 Object model [intro.object]) made std::launder not necessary, where the same use case in C++17 required std::launder, or are they completely orthogonal?"

Honestly, I have not been able to find any cases that where std::launder could compensate implict-lifetime objects. But I found an example were implicit-lifetime object makes std::launder usefull:

  class my_buffer {
      alignas(int) std::byte buffer [2*sizeof(int)];
      
      int * begin(){
         //implictly created array of int inside the buffer
         //nevertheless to get a pointer to this array, 
         //std::launder is necessary as the buffer is not
         //pointer inconvertible with that array
         return *std::launder (reinterpret_cast <int(*)[2]>(&buffer));
         }
      create_int(std::size_t index, int value){
         new (begin()+index) auto{value};
         }
       };
      

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM