在 constexpr 上下文中存储指向未初始化数组的结束指针

Storing end pointer to uninitialized array in constexpr context

我正在尝试创建一个 constexpr 友好的小型缓冲区优化向量类型,它像往常一样存储开始、结束和容量指针,但是当它默认构造时,开始和容量指针在重新分配到堆之前指向本地内存在需要的时候。但是我不知道如何在 constexpr 上下文中存储容量指针,因为我希望它能够支持非平凡构造的类型。为了存储非平凡构造的类型,我不能使用 std::aligned_storage 因为完整的类型擦除不允许我稍后获取元素(因为 reinterpret_cast 是不允许的),所以我决定存储联合数组中的元素(有点像可选的)。像这样存储它允许我稍后通过联合访问获取元素并允许我让数组保持未初始化状态,但是当它存储在联合内时我无法弄清楚如何存储指向容量结束的值指针,因为它没有检测到当指向一个联合值时,整个超出最后一个指针的东西。当然所有这些都需要 c++20.

#include <algorithm>
#include <memory>
#include <utility>
#include <array>

struct Null{};

template<typename T>
union Storage
{
    Null uninitialized;
    T value;

    constexpr Storage()
    : uninitialized{}
    {}

    template<typename... Args>
    constexpr Storage(Args&&... args)
    : value(std::forward<Args>(args)...)
    {}

    constexpr ~Storage(){}
};

template<typename T, size_t N>
struct Vec
{
    std::array<Storage<T>, N> storage;

    T* begin; 
    T* end;   

    constexpr Vec()
    : begin(makeBegin())
    , end(makeEnd())
    {}

    constexpr T* makeBegin()
    {
        return &storage[0].value;
    }
    constexpr T* makeEnd()
    {
        return (&storage[N].value);
    }
};


constexpr bool test()
{
    Vec<std::pair<float, float>, 10> vec{};

    for(auto it = vec.begin; it < vec.end; ++it)
    {
        std::construct_at(it, 10.0f, 10.0f);
    }

    return vec.begin[5] == std::pair{ 10.0f, 10.0f };
}


int main()
{
    static_assert(test());
}

https://godbolt.org/z/46o19qcvP

是否有另一种方法可以在不初始化它们的情况下获取指向存储的非平凡可构造类型(例如数组中的对)的指针?

您的代码存在多个问题:

  • T* 不是您表示的有效迭代器,因为 T 实际上是结构的成员。迭代器需要对数组的值类型进行操作。
  • 使用 storage[N] 是越界访问,即使您只是尝试使用其中成员的地址也是如此。

解决这两个问题的一种方法是使用自定义迭代器类型。这是一个基于您的原始代码的示例(迭代器实现有点不完善 - 我只是实现了编译代码所需的内容):

#include <algorithm>
#include <memory>
#include <utility>
#include <array>


template<typename T>
union Storage
{
    T value;

    constexpr Storage() {}

    template<typename... Args>
    constexpr Storage(Args&&... args)
    : value(std::forward<Args>(args)...)
    {}

    constexpr ~Storage(){}
};

template<typename T, size_t N>
struct Vec
{
    std::array<Storage<T>, N> storage;

    struct iterator {
        Storage<T>* p;
        constexpr T& operator*() { return this->p->value; }
        constexpr T* operator->() { return &this->p->value; }
        constexpr T& operator[](std::size_t n) { return (this->p)[n].value; }
        constexpr iterator& operator++() { ++this->p; return *this; }
        constexpr iterator  operator++(int) { auto rc(*this); ++*this; return rc; }
        constexpr iterator& operator+= (std::size_t n){ this->p += n; return *this; }
        friend constexpr iterator operator+ (iterator it, std::size_t n) { return it += n; }
        constexpr bool      operator== (iterator const&) const = default;
    constexpr bool      operator< (iterator const& other) const { return this->p < other.p; }
    };
    
    
    iterator begin; 
    iterator end;   
    
    constexpr Vec()
    : begin(makeBegin())
    , end(makeEnd())
    {}

    constexpr iterator makeBegin()
    {
        return {&this->storage[0]};
    }
    constexpr iterator makeEnd()
    {
        return this->makeBegin() + N;
    }
};


constexpr bool test()
{
    Vec<std::pair<float, float>, 10> vec{};

    for(auto it = vec.begin; it < vec.end; ++it)
    {
        std::construct_at(&*it, 10.0f, 10.0f);
    }

    return vec.begin[5] == std::pair{ 10.0f, 10.0f };
}


int main()
{   
    static_assert(test());
}

由于 T 元素需要单独创建才能成为活动的 union 成员,因此这里并不是真正存储 T* 对象的方法。这意味着向量的可能可变大小的部分还存储 Storage<T> 元素以避免混淆两种不同的迭代器类型。