Skip to content

Commit 8ac8593

Browse files
committed
separate checked variant into a wrapper
1 parent b1c221b commit 8ac8593

4 files changed

Lines changed: 589 additions & 162 deletions

File tree

flexible_array_checked.hpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#ifndef CPP_MVS_FLEXIBLE_ARRAY_CHECKED_HPP
2+
#define CPP_MVS_FLEXIBLE_ARRAY_CHECKED_HPP
3+
4+
#include "flexible_array_unchecked.hpp"
5+
6+
/// A wrapper around FlexibleArrayUnchecked that provides bounds-checked access to elements.
7+
///
8+
/// This class retrieves the capacity from the header and performs precondition checks to ensure safe access.
9+
template <TrailingElementCountProvider Header, typename Element>
10+
class FlexibleArrayChecked {
11+
private:
12+
FlexibleArrayUnchecked<Header, Element> unchecked_storage;
13+
14+
/// Constructs the FlexibleArrayChecked by taking ownership of an existing unchecked instance
15+
constexpr FlexibleArrayChecked(FlexibleArrayUnchecked<Header, Element>&& unchecked) noexcept
16+
: unchecked_storage(std::move(unchecked)) {}
17+
public:
18+
/// Constructs a buffer with enough space to hold the header and `capacity` number of Elements.
19+
///
20+
/// `init_header` must initialize the header by placement new/`std::construct_at` at the supplied memory address.
21+
[[nodiscard]] static constexpr auto with_header_initialized_by(const Int capacity,
22+
std::invocable<Header*> auto&& init_header) noexcept
23+
-> FlexibleArrayChecked
24+
{
25+
return FlexibleArrayChecked{FlexibleArrayUnchecked<Header, Element>::with_header_initialized_by(capacity, std::forward<decltype(init_header)>(init_header))};
26+
}
27+
28+
/// Constructs a buffer with enough space to hold the header and `capacity` number of Elements.
29+
///
30+
/// The given `header` is moved into the storage.
31+
[[nodiscard]] static constexpr auto with_header(Int const capacity, Header&& header) noexcept -> FlexibleArrayChecked
32+
requires(std::movable<Header>)
33+
{
34+
return with_header_initialized_by(capacity,
35+
[&](Header* place) { std::construct_at(place, std::move(header)); });
36+
}
37+
38+
/// Returns the address for the place of the `i`th element in the array.
39+
///
40+
/// Requires 0 <= `i` < `capacity()`, and the object being in a valid, non-moved-from state.
41+
template <typename Self>
42+
[[nodiscard]] constexpr auto element_address(this Self&& self, const Int i) noexcept
43+
-> const_pointee_like<Self, Element*>
44+
{
45+
precondition(i >= 0 && i < self.capacity(), "Index out of bounds");
46+
return self.unchecked_storage.element_address(i);
47+
}
48+
49+
/// Returns the pointer to the header.
50+
///
51+
/// Requires the FlexibleArrayChecked being in a valid, non-moved-from state.
52+
template <typename Self>
53+
[[nodiscard]] constexpr auto header(this Self&& self) noexcept -> const_pointee_like<Self, Header*>
54+
{
55+
return self.unchecked_storage.header();
56+
}
57+
58+
/// The number of elements the storage has allocated space for.
59+
///
60+
/// Requires the object to be in a valid, non-moved-from state.
61+
[[nodiscard]] constexpr auto capacity() const noexcept -> Int { return unchecked_storage.header()->trailing_element_count(); }
62+
63+
/// Extracts the storage out of the trailing array, handing out the ownership to the callee.
64+
///
65+
/// The original FlexibleCheckedArray will be left in a moved-from state.
66+
[[nodiscard]] constexpr auto extract_storage() -> FlexibleArrayUnchecked<Header, Element> { return std::move(unchecked_storage); }
67+
68+
// Not copyable
69+
FlexibleArrayChecked(const FlexibleArrayChecked& other) = delete;
70+
FlexibleArrayChecked& operator=(const FlexibleArrayChecked& other) = delete;
71+
72+
// Moveable
73+
FlexibleArrayChecked(FlexibleArrayChecked&& other) = default;
74+
FlexibleArrayChecked& operator=(FlexibleArrayChecked&& other) noexcept = default;
75+
~FlexibleArrayChecked() = default;
76+
77+
/// Swaps the underlying storage of `a` and `b`.
78+
friend void swap(FlexibleArrayChecked& a, FlexibleArrayChecked& b) noexcept
79+
{
80+
swap(a.unchecked_storage, b.unchecked_storage);
81+
}
82+
83+
/// Projects a stack-allocated temporary
84+
template <std::invocable<FlexibleArrayChecked&> F>
85+
static constexpr auto project_temporary(const Int element_count, F consumer) -> std::invoke_result_t<F, FlexibleArrayChecked&>
86+
{
87+
return FlexibleArrayUnchecked<Header, Element>::project_temporary(element_count, [&](auto& unchecked) {
88+
// Wrap the unchecked version in a checked wrapper (consume the projected `unchecked` temporarily).
89+
FlexibleArrayChecked checked{std::move(unchecked)};
90+
auto result = consumer(checked);
91+
92+
// After the consumer is done with it, move out the unchecked storage to restore the projection.
93+
unchecked = checked.extract_storage();
94+
return result;
95+
});
96+
}
97+
};
98+
99+
#endif // CPP_MVS_FLEXIBLE_ARRAY_CHECKED_HPP

flexible_array_unchecked.hpp

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#ifndef CPP_MVS_FLEXIBLE_ARRAY_UNCHECKED_HPP
2+
#define CPP_MVS_FLEXIBLE_ARRAY_UNCHECKED_HPP
3+
4+
#include "library.h"
5+
6+
/// A buffer of header and elements stored in a contiguous region of memory, whose size is determined at
7+
/// instance creation.
8+
///
9+
/// Note: After initialization, the header's destruction is managed by the FlexibleArray.
10+
///
11+
/// The FlexibleArray stores its elements out of line, so it is **movable** but **not copyable**.
12+
///
13+
/// Warning: The destructor of `FlexibleArray` does not destroy the elements that may
14+
/// be stored in its payload. You must ensure that they are properly destroyed before destroying this object.
15+
/// Similarly, the initialization of `FlexibleArray` doesn't start the lifetime of its elements, so users must
16+
/// use placement new or std::construct_at to create the object.
17+
template <TrailingElementCountProvider Header, typename Element>
18+
struct FlexibleArrayUnchecked
19+
{
20+
private:
21+
/// Storage containing Header, potential padding, then `capacity` number of elements.
22+
///
23+
/// May be null in case of a moved-from object.
24+
UnsafeMutableRawPointer storage;
25+
26+
/// The offset of the start of the array from the start of the storage, given in bytes.
27+
[[nodiscard]] static constexpr auto elements_offset() noexcept -> Int
28+
{
29+
return align_up(sizeof(Header), alignof(Element));
30+
}
31+
32+
/// The total space required for the storage of `element_count` elements, given in bytes.
33+
///
34+
/// Guaranteed to be a multiple of `Header`'s alignment.
35+
[[nodiscard]] static constexpr auto storage_size_for(const Int element_count) noexcept -> size_t
36+
{
37+
return align_up(static_cast<size_t>(elements_offset() + (sizeof(Element) * element_count)), alignof(Header));
38+
}
39+
40+
/// Constructs a flexible array by taking ownership of an existing storage.
41+
[[nodiscard]] constexpr explicit FlexibleArrayUnchecked(char* const owned_storage) noexcept : storage(owned_storage) {}
42+
43+
/// Returns the address of the first array element.
44+
///
45+
/// Note: There may be no element at the returned address when `capacity() == 0`.
46+
/// Requires the object being in a valid, non-moved-from state.
47+
template <typename Self>
48+
[[nodiscard]] constexpr auto elements_start(this Self&& self) -> const_pointee_like<Self, Element*>
49+
{
50+
return reinterpret_cast<const_pointee_like<Self, Element*>>(self.storage + elements_offset());
51+
}
52+
53+
public:
54+
/// Constructs a buffer with enough space to hold the header and `capacity` number of Elements.
55+
///
56+
/// `init_header` must initialize the header by placement new/std::construct_at at the supplied memory address.
57+
[[nodiscard]] static constexpr auto with_header_initialized_by(Int const capacity,
58+
std::invocable<Header*> auto&& init_header) noexcept
59+
-> FlexibleArrayUnchecked
60+
{
61+
auto* storage = static_cast<char*>(
62+
Detail::aligned_alloc(storage_size_for(capacity), std::max(alignof(Header), alignof(Element))));
63+
init_header(reinterpret_cast<Header*>(storage));
64+
return FlexibleArrayUnchecked{storage};
65+
}
66+
67+
/// Constructs a buffer with enough space to hold the header and `capacity` number of Elements.
68+
///
69+
/// The given `header` is moved into the storage.
70+
[[nodiscard]] static constexpr auto with_header(Int const capacity, Header&& header) noexcept -> FlexibleArrayUnchecked
71+
requires(std::movable<Header>)
72+
{
73+
return with_header_initialized_by(capacity,
74+
[&](Header* place) { std::construct_at(place, std::move(header)); });
75+
}
76+
77+
/// Returns the address for the place of the `i`th element in the array.
78+
///
79+
/// Requires `i` < `capacity()`, and the object being in a valid, non-moved-from state.
80+
template <typename Self>
81+
[[nodiscard]] constexpr auto element_address(this Self&& self, const Int i) noexcept
82+
-> const_pointee_like<Self, Element*>
83+
{
84+
return self.elements_start() + i;
85+
}
86+
87+
/// Returns the pointer to the header.
88+
///
89+
/// Requires the FlexibleArray being in a valid, non-moved-from state.
90+
template <typename Self>
91+
[[nodiscard]] constexpr auto header(this Self&& self) noexcept -> const_pointee_like<Self, Header*>
92+
{
93+
return reinterpret_cast<const_pointee_like<Self, Header*>>(self.storage);
94+
}
95+
96+
/// Destroying the header unless the object is in a moved-from state.
97+
~FlexibleArrayUnchecked()
98+
{
99+
if (storage != nullptr)
100+
{
101+
std::destroy_at(header());
102+
Detail::aligned_free(storage);
103+
}
104+
}
105+
106+
/// Extracts the storage out of the trailing array, handing out the ownership to the callee.
107+
///
108+
/// The underlying storage won't be freed by this FlexibleArray.
109+
[[nodiscard]] constexpr auto leak_storage() -> UnsafeMutableRawPointer { return std::exchange(storage, nullptr); }
110+
111+
// Not copyable
112+
FlexibleArrayUnchecked(const FlexibleArrayUnchecked& other) = delete;
113+
FlexibleArrayUnchecked& operator=(const FlexibleArrayUnchecked& other) = delete;
114+
115+
/// Move constructor
116+
FlexibleArrayUnchecked(FlexibleArrayUnchecked&& other) noexcept : storage(other.storage) { other.storage = nullptr; }
117+
/// Move assignment operator
118+
FlexibleArrayUnchecked& operator=(FlexibleArrayUnchecked&& other) noexcept
119+
{
120+
// Moving to self is a no-op
121+
if (this == &other)
122+
{
123+
return *this;
124+
}
125+
// Destroying the header unless the object was in a moved-from state.
126+
if (storage != nullptr)
127+
{
128+
std::destroy_at(header());
129+
Detail::aligned_free(storage);
130+
}
131+
// Taking ownership of the other object's storage, marking the other object as moved-from.
132+
storage = other.storage;
133+
other.storage = nullptr;
134+
return *this;
135+
}
136+
137+
/// Swaps the underlying storage of `a` and `b`.
138+
friend void swap(FlexibleArrayUnchecked& a, FlexibleArrayUnchecked& b) noexcept { std::swap(a.storage, b.storage); }
139+
140+
/// Projects a stack-allocated temporary
141+
template <std::invocable<FlexibleArrayUnchecked&> F>
142+
static constexpr auto project_temporary(Int element_count, F consumer) -> std::invoke_result_t<F, FlexibleArrayUnchecked&>
143+
{
144+
auto storage_size = FlexibleArrayUnchecked::storage_size_for(element_count);
145+
char* storage = aligned_alloca(storage_size, alignof(Header));
146+
147+
FlexibleArrayUnchecked flexible_array{storage};
148+
auto result = consumer(flexible_array);
149+
150+
std::destroy_at(reinterpret_cast<Header*>(flexible_array.leak_storage()));
151+
152+
return result;
153+
}
154+
};
155+
156+
#endif // CPP_MVS_FLEXIBLE_ARRAY_UNCHECKED_HPP

0 commit comments

Comments
 (0)