|
| 1 | +#ifndef CPP_MVS_LIBRARY_H |
| 2 | +#define CPP_MVS_LIBRARY_H |
| 3 | + |
| 4 | +#include <concepts> |
| 5 | +#include <cstdio> |
| 6 | +#include <cstdlib> |
| 7 | +#include <exception> |
| 8 | +#include <iostream> |
| 9 | +#include <memory> |
| 10 | +#include <string> |
| 11 | +#include <string_view> |
| 12 | +#include <type_traits> |
| 13 | +#include <utility> |
| 14 | + |
| 15 | +using Int = long long; |
| 16 | + |
| 17 | +namespace Detail |
| 18 | +{ |
| 19 | + inline void* aligned_alloc(size_t size, size_t align) |
| 20 | + { |
| 21 | +#ifdef _MSC_VER |
| 22 | + return _aligned_malloc(size, align); |
| 23 | +#else |
| 24 | + return std::aligned_alloc(align, size); |
| 25 | +#endif |
| 26 | + } |
| 27 | + |
| 28 | + inline void aligned_free(void* block) |
| 29 | + { |
| 30 | +#ifdef _MSC_VER |
| 31 | + _aligned_free(block); |
| 32 | +#else |
| 33 | + std::free(block); |
| 34 | +#endif |
| 35 | + } |
| 36 | +} // namespace Detail |
| 37 | +/// Applies const to `TargetType` if `Base` is const. |
| 38 | +template <typename Base, typename TargetType> |
| 39 | +using const_like = std::conditional_t<std::is_const_v<std::remove_reference_t<Base>>, TargetType const, TargetType>; |
| 40 | + |
| 41 | +/// Applies const to the type POINTED TO by TargetType. |
| 42 | +/// Example: T* => T* or T const* depending on whether Base is const |
| 43 | +template <typename Base, typename TargetType> |
| 44 | +using const_pointee_like = std::conditional_t<std::is_const_v<std::remove_reference_t<Base>>, |
| 45 | + // If Base is const: remove pointer (*), make const, add pointer back (*) |
| 46 | + std::add_pointer_t<std::add_const_t<std::remove_pointer_t<TargetType>>>, |
| 47 | + // If Base is not const: keep original |
| 48 | + TargetType>; |
| 49 | + |
| 50 | +constexpr void precondition(const bool p, const std::string_view message = "Precondition failure.") |
| 51 | +{ |
| 52 | + if (!p) |
| 53 | + { |
| 54 | + std::fprintf(stderr, "Assertion failure: %s\n", std::string{message}.c_str()); |
| 55 | + std::quick_exit(EXIT_FAILURE); |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +/// Rounds up 'n' to the next multiple of 'align', assuming `n` and `align` are non-negative integer powers of 2. |
| 60 | +template <std::unsigned_integral T> |
| 61 | +constexpr auto align_up(const T n, T const align) -> T |
| 62 | +{ |
| 63 | + return (n + align - 1) & ~(align - 1); |
| 64 | +} |
| 65 | + |
| 66 | +/// Allocates at least `size` bytes on the stack, ensuring alignment. |
| 67 | +#define aligned_alloca(size, alignment) \ |
| 68 | + reinterpret_cast<char*>( \ |
| 69 | + ::align_up(reinterpret_cast<uintptr_t>(alloca((size) + (alignment))), static_cast<uintptr_t>(alignment))) |
| 70 | + |
| 71 | +template <typename T> |
| 72 | +concept TrailingElementCountProvider = requires(T&& a) { |
| 73 | + /// fun trailing_element_count() -> Int |
| 74 | + { a.trailing_element_count() } -> std::same_as<Int>; |
| 75 | +}; |
| 76 | + |
| 77 | +using UnsafeMutableRawPointer = char*; |
| 78 | + |
| 79 | +/// A buffer of header and elements stored in a contiguous region of memory, whose size is determined at |
| 80 | +/// instance creation. |
| 81 | +/// |
| 82 | +/// Note: After initialization, the header's destruction is managed by the FlexibleArray. |
| 83 | +/// |
| 84 | +/// The FlexibleArray stores its elements out of line, so it is **movable** but **not copyable**. |
| 85 | +/// |
| 86 | +/// Warning: The destructor of `FlexibleArray` does not destroy the elements that may |
| 87 | +/// be stored in its payload. You must ensure that they are properly destroyed before destroying this object. |
| 88 | +/// Similarly, the initialization of `FlexibleArray` doesn't start the lifetime of its elements, so users must |
| 89 | +/// use placement new or std::construct_at to create the object. |
| 90 | +template <TrailingElementCountProvider Header, typename Element> |
| 91 | +struct FlexibleArray |
| 92 | +{ |
| 93 | +private: |
| 94 | + /// Storage containing Header, potential padding, then `capacity` number of elements. |
| 95 | + /// |
| 96 | + /// May be null in case of a moved-from object. |
| 97 | + UnsafeMutableRawPointer storage; |
| 98 | + |
| 99 | + /// The offset of the start of the array from the start of the storage, given in bytes. |
| 100 | + [[nodiscard]] static constexpr auto elements_offset() noexcept -> Int |
| 101 | + { |
| 102 | + return align_up(sizeof(Header), alignof(Element)); |
| 103 | + } |
| 104 | + |
| 105 | + /// The total space required for the storage of `element_count` elements, given in bytes. |
| 106 | + /// |
| 107 | + /// Guaranteed to be a multiple of `Header`'s alignment. |
| 108 | + [[nodiscard]] static constexpr auto storage_size_for(const Int element_count) noexcept -> size_t |
| 109 | + { |
| 110 | + return align_up(elements_offset() + sizeof(Element) * element_count, alignof(Header)); |
| 111 | + } |
| 112 | + |
| 113 | + /// Constructs a flexible array by taking ownership of an existing storage. |
| 114 | + [[nodiscard]] constexpr explicit FlexibleArray(char* const owned_storage) noexcept : storage(owned_storage) {} |
| 115 | + |
| 116 | + /// Returns the address of the first array element. |
| 117 | + /// |
| 118 | + /// Note: There may be no element at the returned address when `capacity() == 0`. |
| 119 | + /// Requires the object being in a valid, non-moved-from state. |
| 120 | + template <typename Self> |
| 121 | + [[nodiscard]] constexpr auto elements_start(this Self&& self) -> const_pointee_like<Self, Element*> |
| 122 | + { |
| 123 | + return reinterpret_cast<const_pointee_like<Self, Element*>>(self.storage + elements_offset()); |
| 124 | + } |
| 125 | + |
| 126 | +public: |
| 127 | + /// Constructs a buffer with enough space to hold the header and `capacity` number of Elements. |
| 128 | + /// |
| 129 | + /// `init_header` must initialize the header by placement new/std::construct_at at the supplied memory address. |
| 130 | + [[nodiscard]] static constexpr auto with_header_initialized_by(Int const capacity, |
| 131 | + std::invocable<Header*> auto&& init_header) noexcept |
| 132 | + -> FlexibleArray |
| 133 | + { |
| 134 | + auto* storage = static_cast<char*>( |
| 135 | + Detail::aligned_alloc(storage_size_for(capacity), std::max(alignof(Header), alignof(Element)))); |
| 136 | + init_header(reinterpret_cast<Header*>(storage)); |
| 137 | + return FlexibleArray{storage}; |
| 138 | + } |
| 139 | + |
| 140 | + /// Constructs a buffer with enough space to hold the header and `capacity` number of Elements. |
| 141 | + /// |
| 142 | + /// The given `header` is moved into the storage. |
| 143 | + [[nodiscard]] static constexpr auto with_header(Int const capacity, Header&& header) noexcept -> FlexibleArray |
| 144 | + requires(std::movable<Header>) |
| 145 | + { |
| 146 | + return with_header_initialized_by(capacity, |
| 147 | + [&](Header* place) { std::construct_at(place, std::move(header)); }); |
| 148 | + } |
| 149 | + |
| 150 | + /// Returns the address for the place of the `i`th element in the array. |
| 151 | + /// |
| 152 | + /// Requires `i` < `capacity()`, and the object being in a valid, non-moved-from state. |
| 153 | + template <typename Self> |
| 154 | + [[nodiscard]] constexpr auto element_address(this Self&& self, const Int i) noexcept |
| 155 | + -> const_pointee_like<Self, Element*> |
| 156 | + { |
| 157 | + return self.elements_start() + i; |
| 158 | + } |
| 159 | + |
| 160 | + /// Returns the pointer to the header. |
| 161 | + /// |
| 162 | + /// Requires the FlexibleArray being in a valid, non-moved-from state. |
| 163 | + template <typename Self> |
| 164 | + [[nodiscard]] constexpr auto header(this Self&& self) noexcept -> const_pointee_like<Self, Header*> |
| 165 | + { |
| 166 | + precondition(self.storage != nullptr, "FlexibleArray storage must not be null."); |
| 167 | + return reinterpret_cast<const_pointee_like<Self, Header*>>(self.storage); |
| 168 | + } |
| 169 | + |
| 170 | + /// The number of elements the storage has allocated space for. |
| 171 | + /// |
| 172 | + /// Requires the object to be in a valid, non-moved-from state. |
| 173 | + [[nodiscard]] constexpr auto capacity() const noexcept -> Int { return header()->trailing_element_count(); } |
| 174 | + |
| 175 | + /// Destroying the header unless the object is in a moved-from state. |
| 176 | + ~FlexibleArray() |
| 177 | + { |
| 178 | + if (storage != nullptr) |
| 179 | + { |
| 180 | + std::destroy_at(header()); |
| 181 | + Detail::aligned_free(storage); |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + /// Extracts the storage out of the trailing array, handing out the ownership to the callee. |
| 186 | + /// |
| 187 | + /// The underlying storage won't be freed by this FlexibleArray. |
| 188 | + [[nodiscard]] constexpr auto leak_storage() -> UnsafeMutableRawPointer { return std::exchange(storage, nullptr); } |
| 189 | + |
| 190 | + // Not copyable |
| 191 | + FlexibleArray(const FlexibleArray& other) = delete; |
| 192 | + FlexibleArray& operator=(const FlexibleArray& other) = delete; |
| 193 | + |
| 194 | + /// Move constructor |
| 195 | + FlexibleArray(FlexibleArray&& other) noexcept : storage(other.storage) { other.storage = nullptr; } |
| 196 | + /// Move assignment operator |
| 197 | + FlexibleArray& operator=(FlexibleArray&& other) noexcept |
| 198 | + { |
| 199 | + // Moving to self is a no-op |
| 200 | + if (this == &other) |
| 201 | + return *this; |
| 202 | + // Destroying the header unless the object was in a moved-from state. |
| 203 | + if (storage != nullptr) |
| 204 | + { |
| 205 | + std::destroy_at(header()); |
| 206 | + Detail::aligned_free(storage); |
| 207 | + } |
| 208 | + // Taking ownership of the other object's storage, marking the other object as moved-from. |
| 209 | + storage = other.storage; |
| 210 | + other.storage = nullptr; |
| 211 | + return *this; |
| 212 | + } |
| 213 | + |
| 214 | + /// Swaps the underlying storage of `a` and `b`. |
| 215 | + friend void swap(FlexibleArray& a, FlexibleArray& b) noexcept { std::swap(a.storage, b.storage); } |
| 216 | + |
| 217 | + /// Projects a stack-allocated temporary |
| 218 | + template <std::invocable<FlexibleArray&> F> |
| 219 | + static constexpr auto project_temporary(Int element_count, F consumer) -> std::invoke_result_t<F, FlexibleArray&> |
| 220 | + { |
| 221 | + auto storage_size = FlexibleArray::storage_size_for(element_count); |
| 222 | + char* storage = aligned_alloca(storage_size, alignof(Header)); |
| 223 | + |
| 224 | + FlexibleArray flexible_array{storage}; |
| 225 | + auto result = consumer(flexible_array); |
| 226 | + |
| 227 | + std::destroy_at(reinterpret_cast<Header*>(flexible_array.leak_storage())); |
| 228 | + |
| 229 | + return result; |
| 230 | + } |
| 231 | +}; |
| 232 | + |
| 233 | + |
| 234 | +// |
| 235 | +// void test_f() |
| 236 | +// { |
| 237 | +// for (Int i = 0; i <= 64; i += 8) |
| 238 | +// { |
| 239 | +// size_t x = static_cast<size_t>(i); |
| 240 | +// size_t header_stride = 16; |
| 241 | +// size_t element_align = 32; |
| 242 | +// |
| 243 | +// auto offset_with_known_initial_offset = align_up(x + header_stride, element_align) - x; |
| 244 | +// auto offset_without_known_initial_offset = align_up(header_stride, element_align); |
| 245 | +// std::cout << x << " | " << offset_without_known_initial_offset << " " << offset_with_known_initial_offset |
| 246 | +// << std::endl; |
| 247 | +// } |
| 248 | +// } |
| 249 | + |
| 250 | +/// A dynamically sized buffer with bounds checked element access. |
| 251 | +template <typename Element> // |
| 252 | +class UnsafeBufferPointer |
| 253 | +{ |
| 254 | + Element* start_; |
| 255 | + Int count_; |
| 256 | + |
| 257 | +public: |
| 258 | + explicit constexpr UnsafeBufferPointer(Element* start, const Int count) : start_(start), count_(count) {} |
| 259 | + |
| 260 | + template <typename Self> |
| 261 | + constexpr auto&& operator[](this Self&& self, Int index) noexcept |
| 262 | + { |
| 263 | + precondition(index >= 0 && index < self.count_); |
| 264 | + return std::forward_like<Self>(*(self.start_ + index)); |
| 265 | + } |
| 266 | + |
| 267 | + [[nodiscard]] Int count() const noexcept { return count_; } |
| 268 | +}; |
| 269 | +#endif // CPP_MVS_LIBRARY_H |
0 commit comments