簡體   English   中英

在 constexpr 上下文中存儲指向未初始化數組的結束指針

[英]Storing end pointer to uninitialized array in constexpr context

我正在嘗試創建一個 constexpr 友好的小型緩沖區優化向量類型,它像往常一樣存儲開始、結束和容量指針,但是當它默認構造時,開始和容量指針指向本地 memory,然后在需要時重新分配到堆。 但是我無法弄清楚如何在 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

有沒有另一種方法可以在不初始化數組的情況下獲取指向存儲的非平凡可構造類型的指針,例如數組中的對?

您的代碼存在多個問題:

  1. T*不是您的表示的有效迭代器,因為T實際上是結構的成員。 迭代器需要對array的值類型進行操作。
  2. 使用storage[N]是一種越界訪問,即使您只是嘗試使用其中成員的地址。

解決這兩個問題的一種方法是使用自定義迭代器類型。 這是一個基於您的原始代碼的示例(迭代器實現有點不競爭 - 我剛剛實現了編譯代碼所需的內容):

#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;

    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*我可以想象它可以使用帶有T array[N]成員的union (而不是使用std::array<T, N>並單獨構造這些元素。因為那不是真正的問題(只是對問題的評論)我沒有玩過。

說實話,不確定我是否完全理解你想要什么,但如果你正在尋找一個類型可以未初始化的編譯時容器,你可以使用std::variant作為你的元素類型。

為了存儲在構造容器時不會初始化的非平凡可構造類型,您將第一個替代設置為std::monostate monostate (這類似於您的Null類型)。

我在下面的 API 的一個問題是它有點不一致; 例如,有時我返回一個std::variant ,有時我返回一個指向替代的指針(或null ,如果指定索引處的std::variant沒有用值初始化)。

當然,您可以更改 API 以滿足您的需要(例如始終通過引用返回std::variant並允許調用者測試它是否包含“有效”值,如果不包含,則設置它)。

template<typename T, std::size_t N>
class cvec 
{ 
    using element_type = std::variant<std::monostate, T>;
    using size_type = std::size_t;

public:
    template<typename ...Args>
    constexpr void emplace_at( size_type index, Args&& ...args ) {
        buffer_[ index ].template emplace<T>( std::forward<Args>( args )... );
    }

    constexpr void insert( size_type index, T&& value )
    { buffer_[ index ] = std::move( value ); }

    constexpr void insert( size_type index, const T& value )
    { buffer_[ index ] = value; }

    constexpr auto try_get_at( size_type index )
    { return std::get_if<T>( &buffer_[ index ] ); }

    constexpr decltype( auto ) end( )
    { return std::end( buffer_ ); }

    constexpr decltype( auto ) begin( )
    { return std::begin( buffer_ ); }

    constexpr decltype( auto ) operator[ ]( size_type index ) 
    { return buffer_[ index ]; }

private:
    std::array<element_type, N> buffer_{ };
};

consteval bool test( )
{
    using pair = std::pair<float, float>;
    cvec<pair, 10> vec{ };

    for( int i = 0; i < 10; i++ )
    {
        vec.emplace_at( i, 10.0f, 10.0f );
    }
    return *vec.try_get_at( 5 ) == std::pair{ 10.0f, 10.0f };
}



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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM