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



我正试图创建一个constexpr友好的小缓冲区优化向量类型,它像往常一样存储开始、结束和容量指针,但当默认构造时,开始和容量指针指向本地内存,然后在需要时重新分配到堆。然而,我不知道如何将容量指针存储在constexpr上下文中,因为我希望它能够支持非平凡构造的类型。为了存储非平凡构造的类型,我不能使用std::aligned_storage,因为完整的类型擦除不允许我稍后获得元素(因为不允许重新解释_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>元素,以避免干扰两种不同的迭代器类型。

最新更新