diff --git a/Source/Core/Common/SPSCQueue.h b/Source/Core/Common/SPSCQueue.h index b586012226..d54ac303d4 100644 --- a/Source/Core/Common/SPSCQueue.h +++ b/Source/Core/Common/SPSCQueue.h @@ -8,7 +8,8 @@ #include #include -#include + +#include "Common/TypeUtils.h" namespace Common { @@ -38,7 +39,7 @@ public: template void Emplace(Args&&... args) { - std::construct_at(&m_write_ptr->value.data, std::forward(args)...); + m_write_ptr->value.Construct(std::forward(args)...); Node* const new_ptr = new Node; m_write_ptr->next = new_ptr; @@ -54,14 +55,14 @@ public: } // The following are only safe from the "consumer thread": - T& Front() { return m_read_ptr->value.data; } - const T& Front() const { return m_read_ptr->value.data; } + T& Front() { return m_read_ptr->value.Ref(); } + const T& Front() const { return m_read_ptr->value.Ref(); } void Pop() { assert(!Empty()); - std::destroy_at(&Front()); + m_read_ptr->value.Destroy(); Node* const old_node = m_read_ptr; m_read_ptr = old_node->next; @@ -94,14 +95,7 @@ public: private: struct Node { - // union allows value construction to be deferred until Push. - union Value - { - T data; - Value() {} - ~Value() {} - } value; - + ManuallyConstructedValue value; Node* next; }; diff --git a/Source/Core/Common/SmallVector.h b/Source/Core/Common/SmallVector.h index d06bcd1cfa..9200fe2cde 100644 --- a/Source/Core/Common/SmallVector.h +++ b/Source/Core/Common/SmallVector.h @@ -7,9 +7,13 @@ #include #include +#include "Common/TypeUtils.h" + namespace Common { +// TODO C++26: Replace with std::inplace_vector. + // An std::vector-like container that uses no heap allocations but is limited to a maximum size. template class SmallVector final @@ -59,13 +63,13 @@ public: value_type& emplace_back(Args&&... args) { assert(m_size < MaxSize); - return *::new (&m_array[m_size++ * sizeof(value_type)]) value_type{std::forward(args)...}; + return m_array[m_size++].Construct(std::forward(args)...); } void pop_back() { assert(m_size > 0); - std::destroy_at(data() + --m_size); + m_array[--m_size].Destroy(); } value_type& operator[](size_t i) @@ -79,11 +83,11 @@ public: return data()[i]; } - auto data() { return std::launder(reinterpret_cast(m_array.data())); } + auto data() { return m_array.data()->Ptr(); } auto begin() { return data(); } auto end() { return data() + m_size; } - auto data() const { return std::launder(reinterpret_cast(m_array.data())); } + auto data() const { return m_array.data()->Ptr(); } auto begin() const { return data(); } auto end() const { return data() + m_size; } @@ -106,7 +110,7 @@ public: void clear() { resize(0); } private: - alignas(value_type) std::array m_array; + std::array, MaxSize> m_array; size_t m_size = 0; }; diff --git a/Source/Core/Common/TypeUtils.h b/Source/Core/Common/TypeUtils.h index 714f5a718d..384af63b8c 100644 --- a/Source/Core/Common/TypeUtils.h +++ b/Source/Core/Common/TypeUtils.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include namespace Common @@ -82,4 +83,54 @@ static_assert(!IsNOf::value); static_assert(IsNOf::value); static_assert(IsNOf::value); // Type conversions ARE allowed static_assert(!IsNOf::value); + +// Lighter than optional but you must manage object lifetime yourself. +// You must call Destroy if you call Construct. +// Useful for containers. +template +class ManuallyConstructedValue +{ +public: + template + T& Construct(Args&&... args) + { + static_assert(sizeof(ManuallyConstructedValue) == sizeof(T)); + +// TODO: Remove placement-new version when we can require Clang 16. +#if defined(__cpp_aggregate_paren_init) && (__cpp_aggregate_paren_init >= 201902L) + return *std::construct_at(&m_value.data, std::forward(args)...); +#else + return *::new (&m_value.data) T{std::forward(args)...}; +#endif + } + + void Destroy() { std::destroy_at(&m_value.data); } + + T* Ptr() { return &m_value.data; } + const T* Ptr() const { return &m_value.data; } + T& Ref() { return m_value.data; } + const T& Ref() const { return m_value.data; } + + T* operator->() { return Ptr(); } + const T* operator->() const { return Ptr(); } + T& operator*() { return Ref(); } + const T& operator*() const { return Ref(); } + +private: + union Value + { + // The union allows this object's automatic construction to be avoided. + T data; + + Value() {} + ~Value() {} + + Value& operator=(const Value&) = delete; + Value(const Value&) = delete; + Value& operator=(Value&&) = delete; + Value(Value&&) = delete; + + } m_value; +}; + } // namespace Common