Merge pull request #13587 from jordan-woyak/manual-value

Common: Move some duplicate container element construction logic into a ManuallyConstructedValue template.
This commit is contained in:
Admiral H. Curtiss
2025-04-30 23:45:33 +02:00
committed by GitHub
3 changed files with 67 additions and 18 deletions

View File

@ -8,7 +8,8 @@
#include <atomic>
#include <cassert>
#include <memory>
#include "Common/TypeUtils.h"
namespace Common
{
@ -38,7 +39,7 @@ public:
template <typename... Args>
void Emplace(Args&&... args)
{
std::construct_at(&m_write_ptr->value.data, std::forward<Args>(args)...);
m_write_ptr->value.Construct(std::forward<Args>(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<T> value;
Node* next;
};

View File

@ -7,9 +7,13 @@
#include <cstddef>
#include <utility>
#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 <typename T, size_t MaxSize>
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>(args)...};
return m_array[m_size++].Construct(std::forward<Args>(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<value_type*>(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<const value_type*>(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<std::byte, MaxSize * sizeof(value_type)> m_array;
std::array<ManuallyConstructedValue<T>, MaxSize> m_array;
size_t m_size = 0;
};

View File

@ -3,6 +3,7 @@
#pragma once
#include <cstddef>
#include <memory>
#include <type_traits>
namespace Common
@ -82,4 +83,54 @@ static_assert(!IsNOf<int, 1, int, int>::value);
static_assert(IsNOf<int, 2, int, int>::value);
static_assert(IsNOf<int, 2, int, short>::value); // Type conversions ARE allowed
static_assert(!IsNOf<int, 2, int, char*>::value);
// Lighter than optional<T> but you must manage object lifetime yourself.
// You must call Destroy if you call Construct.
// Useful for containers.
template <typename T>
class ManuallyConstructedValue
{
public:
template <typename... Args>
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>(args)...);
#else
return *::new (&m_value.data) T{std::forward<Args>(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