C++20 中的 std::launder 用例

Ami*_*rsh 7 c++ language-lawyer c++20 stdlaunder

[1]

\n\n

是否有任何情况不需要将p0593r6添加到 C++20 ( \xc2\xa7 6.7.2.11对象模型[intro.object] ) std::launder,而需要 C++17 中的相同用例std::launder,或者它们是完全正交?

\n\n
\n\n

[2]

\n\n

[ptr::launder]规范中的示例是:

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

@Nicol Bolas在这个 SO 答案中给出了另一个例子,使用指向有效存储但类型不同的指针:

\n\n
aligned_storage<sizeof(int), alignof(int)>::type data;\nnew(&data) int;\nint *p = std::launder(reinterpret_cast<int*>(&data));\n
Run Code Online (Sandbox Code Playgroud)\n\n

是否还有其他用例,与允许铸造两个不可透明替换的对象无关,以供使用std::launder

\n\n

具体来说:

\n\n
    \n
  • 是否将reinterpret_cast从A*转换为B*,两者都是指针可相互转换的std::launder,在任何情况下都可能需要使用?(即两个指针可以是指针可相互转换的,但不能透明地替换吗?规范在这两个术语之间没有关联)。
  • \n
  • reinterpret_castvoid*到 T*是否需要使用std::launder
  • \n
  • 下面的代码是否需要使用std::launder?如果是这样,规范中在什么情况下会要求这样做?
  • \n
\n\n

受此讨论启发的带有参考成员的结构:

\n\n
struct A {\n    constexpr A(int &x) : ref(x) {}\n    int &ref;\n};\n\nint main() {\n    int n1 = 1, n2 = 2;\n    A a { n1 };\n    a.~A();\n    new (&a) A {n2};\n    a.ref = 3; // do we need to launder somebody here?\n    std::cout << a.ref << \' \' << n1 << \' \' << n2 << std::endl;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Oli*_*liv 3

在 C++17 之前,具有给定地址和类型的指针始终指向位于该地址的该类型的对象,前提是代码遵循 [basic.life] 的规则。(请参阅:自 C++17 以来,具有正确地址和类型的指针是否仍然始终是有效指针?)。

\n

但在 C++17 标准中为指针值添加了新的特性。这种质量不在指针类型内进行编码,而是直接限定值,与类型无关(可追溯性也是如此)。它在[basic.compound]/3中描述

\n
\n

指针类型的每个值都是以下之一:

\n
    \n
  • 指向对象或函数的指针(该指针被称为指向对象或函数),或者

    \n
  • \n
  • 超过对象末尾的指针 ([expr.add]),或

    \n
  • \n
  • 该类型的空指针值,或\n无效的指针值。

    \n
  • \n
\n
\n

reinterpret_cast指针值的这种性质有其自己的语义(转换规则),下一段描述了它的情况:

\n
\n

如果两个对象是指针可相互转换的,那么它们具有相同的地址,并且可以通过reinterpret_\xc2\xadcast从指向另一个对象的指针获得指向另一个对象的指针。

\n
\n

在[basic-life]中,我们可以找到另一个规则,描述了当对象存储被重用时如何转变这种质量:

\n
\n

如果一个对象的生命周期结束后,在该对象占用的存储空间被重用或释放之前,在原对象占用的存储位置上创建了一个新对象,一个指向原对象的指针,一个指向该对象引用引用原始对象,或者原始对象的名称将自动引用新对象,并且,[...]

\n
\n

正如您所看到的,“指向对象的指针”质量附加到特定对象。

\n

这意味着在您给出的第一个示例的下面的变体中,reinterpret_cast不允许我们不使用指针优化屏障:

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

Areinterpret_cast不是指针优化障碍:reinterpret_cast <int*>(p)指向被破坏对象的成员。

\n

另一种设想方式是,只要reinterpret_cast对象是指针可相互转换的,或者将其强制转换为 void,然后返回到指针可相互转换类型,则“指针”质量就会得到保留。(参见[exp.static_cast]/13)。所以reinterpret_cast <int*>(reinterpret_cast <void*>(p))仍然指向被破坏的对象。

\n

对于您给出的最后一个示例,该名称a引用一个非常量完整对象,因此原始对象a可以透明地由新对象替换。

\n
\n

对于您问的第一个问题:“是否存在将 p0593r6 添加到 C++20 (\xc2\xa7 6.7.2.11 对象模型 [intro.object]) 中使得 std::launder 不必要的情况,其中相同C++17 中的用例需要 std::launder,还是它们完全正交?”

\n

老实说,我还没有找到任何 std::launder 可以补偿隐式生命周期对象的情况。但我发现一个例子是隐式生命周期对象使 std::launder 有用:

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