在没有 std::launder 的情况下将 std::aligned_storage* 重新解释为 T* 是否违反严格别名规则?

xsk*_*xzr 6 c++ strict-aliasing language-lawyer reinterpret-cast

以下示例来自cppreference.com 的std::aligned_storage 页面

#include <iostream>
#include <type_traits>
#include <string>

template<class T, std::size_t N>
class static_vector
{
    // properly aligned uninitialized storage for N T's
    typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
    std::size_t m_size = 0;

public:
    // Create an object in aligned storage
    template<typename ...Args> void emplace_back(Args&&... args) 
    {
        if( m_size >= N ) // possible error handling
            throw std::bad_alloc{};
        new(data+m_size) T(std::forward<Args>(args)...);
        ++m_size;
    }

    // Access an object in aligned storage
    const T& operator[](std::size_t pos) const 
    {
        return *reinterpret_cast<const T*>(data+pos);
    }

    // Delete objects from aligned storage
    ~static_vector() 
    {
        for(std::size_t pos = 0; pos < m_size; ++pos) {
            reinterpret_cast<T*>(data+pos)->~T();
        }
    }
};

int main()
{
    static_vector<std::string, 10> v1;
    v1.emplace_back(5, '*');
    v1.emplace_back(10, '*');
    std::cout << v1[0] << '\n' << v1[1] << '\n';
}
Run Code Online (Sandbox Code Playgroud)

在示例中,operator[]just reinterpret_castsstd::aligned_storage*T*without std:launder,并直接执行间接。然而,根据这个问题,这似乎是未定义的,即使T已经创建了一个类型的对象。

所以我的问题是:示例程序是否真的违反了严格的别名规则?如果没有,我的理解有什么问题?

xsk*_*xzr 6

我在 ISO C++ 标准 - 讨论论坛中提出了一个相关问题。我从那些讨论中学到了答案,写在这里希望能帮助其他对这个问题感到困惑的人。我将根据这些讨论不断更新此答案。

P0137之前,参考[basic.compound]第3段:

如果类型 T 的对象位于地址 A,则称其值为地址 A 的类型为 cv T* 的指针指向该对象,而不管该值是如何获得的。

和 [expr.static.cast] 第 13 段:

如果原始指针值表示内存中一个字节的地址A,并且A满足T的对齐要求,则结果指针值表示与原始指针值相同的地址,即A。

该表达式reinterpret_cast<const T*>(data+pos)表示之前创建的类型为 的对象的地址T,因此指向该对象。间接通过这个指针确实得到了那个对象,这是明确定义的。

然而,在 P0137 之后,指针值的定义发生了变化,并且删除了第一个块引用字。现在参考[basic.compound]第3段:

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

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

  • ...

和 [expr.static.cast] 第 13 段:

如果原始指针值表示内存中一个字节的地址 A 并且 A 不满足 T 的对齐要求,则结果指针值是未指定的。否则,如果原始指针值指向对象 a,并且有一个 T 类型的对象 b(忽略 cv 限定)与 a 的指针可相互转换,则结果是指向 b 的指针。否则,指针值不会因转换而改变

表达式reinterpret_cast<const T*>(data+pos)仍然指向 type 的对象std::aligned_storage<...>::type,并且间接获得一个引用该对象的左值,尽管左值的类型是const Tv1[0]示例中表达式的求值尝试std::aligned_storage<...>::type通过左值访问对象的值,根据 [basic.lval] 第 11 段(即严格别名规则),这是未定义的行为:

如果程序尝试通过以下类型之一以外的泛左值访问对象的存储值,则行为未定义:

  • 对象的动态类型,

  • 对象的动态类型的 cv 限定版本,

  • 与对象的动态类型相似的类型(在 [conv.qual] 中定义),

  • 一种类型,它是与对象的动态类型对应的有符号或无符号类型,

  • 一种类型,它是与对象的动态类型的 cv 限定版本相对应的有符号或无符号类型,

  • 在其元素或非静态数据成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含联合的元素或非静态数据成员),

  • 一个类型,它是对象的动态类型的(可能是 cv 限定的)基类类型,

  • char、unsigned char 或 std?::?byte 类型。