Skip to content

Commit dd0ad3e

Browse files
committed
Initial commit
0 parents  commit dd0ad3e

6 files changed

Lines changed: 572 additions & 0 deletions

File tree

,gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cmake-build-debug

.clang-format

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
Language: Cpp
3+
BasedOnStyle: LLVM
4+
AccessModifierOffset: -4
5+
AlignConsecutiveAssignments: false
6+
AlignConsecutiveDeclarations: false
7+
AlignOperands: false
8+
AlignTrailingComments: false
9+
AlwaysBreakTemplateDeclarations: Yes
10+
BraceWrapping:
11+
AfterCaseLabel: true
12+
AfterClass: true
13+
AfterControlStatement: true
14+
AfterEnum: true
15+
AfterFunction: true
16+
AfterNamespace: true
17+
AfterStruct: true
18+
AfterUnion: true
19+
AfterExternBlock: false
20+
BeforeCatch: true
21+
BeforeElse: true
22+
BeforeLambdaBody: true
23+
BeforeWhile: true
24+
SplitEmptyFunction: true
25+
SplitEmptyRecord: true
26+
SplitEmptyNamespace: true
27+
BreakBeforeBraces: Custom
28+
BreakConstructorInitializers: AfterColon
29+
BreakConstructorInitializersBeforeComma: false
30+
ColumnLimit: 120
31+
ConstructorInitializerAllOnOneLineOrOnePerLine: false
32+
IncludeCategories:
33+
- Regex: '^<.*'
34+
Priority: 1
35+
- Regex: '^".*'
36+
Priority: 2
37+
- Regex: '.*'
38+
Priority: 3
39+
IncludeIsMainRegex: '([-_](test|unittest))?$'
40+
IndentCaseBlocks: true
41+
IndentWidth: 4
42+
InsertNewlineAtEOF: true
43+
MacroBlockBegin: ''
44+
MacroBlockEnd: ''
45+
MaxEmptyLinesToKeep: 2
46+
NamespaceIndentation: All
47+
PointerAlignment: Left
48+
SpaceInEmptyParentheses: false
49+
SpacesInAngles: false
50+
SpacesInConditionalStatement: false
51+
SpacesInCStyleCastParentheses: false
52+
SpacesInParentheses: false
53+
TabWidth: 4
54+
...

CMakeLists.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
cmake_minimum_required(VERSION 3.28)
2+
project(cpp_mvs)
3+
4+
set(CMAKE_CXX_STANDARD 23)
5+
6+
# Setup CPM
7+
file(DOWNLOAD
8+
https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.40.2/CPM.cmake
9+
${CMAKE_BINARY_DIR}/cmake/CPM.cmake
10+
EXPECTED_HASH SHA256=c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d
11+
)
12+
include(${CMAKE_BINARY_DIR}/cmake/CPM.cmake)
13+
14+
# Add doctest
15+
CPMAddPackage(
16+
NAME doctest
17+
GITHUB_REPOSITORY doctest/doctest
18+
VERSION 2.4.12
19+
)
20+
21+
add_library(cpp_mvs STATIC library.cpp)
22+
23+
add_executable(unit_tests tests.cpp)
24+
target_link_libraries(unit_tests PRIVATE cpp_mvs doctest::doctest)
25+
26+
enable_testing()
27+
add_test(NAME unit_tests COMMAND unit_tests)

library.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#include "library.h"
2+
3+
#include <iostream>
4+

library.h

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
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

Comments
 (0)