Skip to content

Commit d8d3dc3

Browse files
committed
merge develop
2 parents e57bf32 + 9d18263 commit d8d3dc3

9 files changed

+293
-43
lines changed

.github/workflows/code_testing.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jobs:
6464
uses: dice-group/cpp-conan-release-reusable-workflow/.github/actions/add_conan_provider@main
6565

6666
- name: Configure CMake
67-
run: cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" -DCMAKE_BUILD_TYPE=Debug -DWITH_SVECTOR=ON -DWITH_BOOST=ON -DBUILD_TESTING=On -DBUILD_EXAMPLES=On -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Ninja -B build .
67+
run: cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" -DCMAKE_BUILD_TYPE=Debug -DWITH_SVECTOR=ON -DWITH_BOOST=ON -DBUILD_TESTING=On -DBUILD_EXAMPLES=On -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Ninja -B build .
6868
env:
6969
CC: ${{ steps.install_cc.outputs.cc }}
7070
CXX: ${{ steps.install_cc.outputs.cxx }}

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ It contains:
99
- `integral_template_variant`: A wrapper type for `std::variant` guarantees to only contain variants of the form `T<ix>` where $\texttt{ix}\in [\texttt{first},\texttt{last}]$ (inclusive).
1010
- `for_{types,values,range}`: Compile time for loops for types, values or ranges
1111
- `polymorphic_allocator`: Like `std::pmr::polymorphic_allocator` but with static dispatch
12+
- `limit_allocator`: Allocator wrapper that limits the amount of memory that is allowed to be allocated
1213
- `DICE_DEFER`/`DICE_DEFER_TO_SUCCES`/`DICE_DEFER_TO_FAIL`: On-the-fly RAII for types that do not support it natively (similar to go's `defer` keyword)
1314
- `overloaded`: Composition for `std::variant` visitor lambdas
1415
- `flex_array`: A combination of `std::array`, `std::span` and a `vector` with small buffer optimization
@@ -64,6 +65,10 @@ The problem with `mmap` allocations is that they will be placed at an arbitrary
6465
therefore absolute pointers will cause segfaults if the segment is reloaded.
6566
Which means: vtables will not work (because they use absolute pointers) and therefore you cannot use `std::pmr::polymorphic_allocator`.
6667

68+
### `limit_allocator`
69+
Allocator wrapper that limits the amount of memory that can be allocated through the inner allocator.
70+
If the limit is exceeded it will throw `std::bad_alloc`.
71+
6772
### `DICE_DEFER`/`DICE_DEFER_TO_SUCCES`/`DICE_DEFER_TO_FAIL`
6873
A mechanism similar to go's `defer` keyword, which can be used to defer some action to scope exit.
6974
The primary use-case for this is on-the-fly RAII-like resource management for types that do not support RAII (for example C types).

examples/CMakeLists.txt

+7
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,10 @@ target_link_libraries(example_variant2
8080
dice-template-library::dice-template-library
8181
)
8282

83+
add_executable(example_limit_allocator
84+
example_limit_allocator.cpp)
85+
target_link_libraries(example_limit_allocator
86+
PRIVATE
87+
dice-template-library::dice-template-library
88+
)
89+

examples/example_limit_allocator.cpp

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <dice/template-library/limit_allocator.hpp>
2+
3+
#include <cassert>
4+
#include <vector>
5+
6+
7+
int main() {
8+
std::vector<int, dice::template_library::limit_allocator<int>> vec{dice::template_library::limit_allocator<int>{3 * sizeof(int)}};
9+
vec.push_back(1);
10+
vec.push_back(2);
11+
12+
try {
13+
vec.push_back(3);
14+
assert(false);
15+
} catch (...) {
16+
}
17+
18+
vec.pop_back();
19+
vec.push_back(4);
20+
21+
try {
22+
vec.push_back(5);
23+
assert(false);
24+
} catch (...) {
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#ifndef DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP
2+
#define DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP
3+
4+
#include <atomic>
5+
#include <cstddef>
6+
#include <memory>
7+
#include <new>
8+
#include <type_traits>
9+
#include <utility>
10+
11+
namespace dice::template_library {
12+
13+
/**
14+
* The synchronization policy of a limit_allocator
15+
*/
16+
enum struct limit_allocator_syncness : bool {
17+
sync, ///< thread-safe (synchronized)
18+
unsync, ///< not thread-safe (unsynchronized)
19+
};
20+
21+
namespace detail_limit_allocator {
22+
template<limit_allocator_syncness syncness>
23+
struct limit_allocator_control_block;
24+
25+
template<>
26+
struct limit_allocator_control_block<limit_allocator_syncness::sync> {
27+
std::atomic<size_t> bytes_left = 0;
28+
29+
void allocate(size_t n_bytes) {
30+
auto old = bytes_left.load(std::memory_order_relaxed);
31+
32+
do {
33+
if (old < n_bytes) [[unlikely]] {
34+
throw std::bad_alloc{};
35+
}
36+
} while (!bytes_left.compare_exchange_weak(old, old - n_bytes, std::memory_order_relaxed, std::memory_order_relaxed));
37+
}
38+
39+
void deallocate(size_t n_bytes) noexcept {
40+
bytes_left.fetch_add(n_bytes, std::memory_order_relaxed);
41+
}
42+
};
43+
44+
template<>
45+
struct limit_allocator_control_block<limit_allocator_syncness::unsync> {
46+
size_t bytes_left = 0;
47+
48+
void allocate(size_t n_bytes) {
49+
if (bytes_left < n_bytes) [[unlikely]] {
50+
throw std::bad_alloc{};
51+
}
52+
bytes_left -= n_bytes;
53+
}
54+
55+
void deallocate(size_t n_bytes) noexcept {
56+
bytes_left += n_bytes;
57+
}
58+
};
59+
}// namespace detail_limit_allocator
60+
61+
/**
62+
* Allocator wrapper that limits the amount of memory its underlying allocator
63+
* is allowed to allocate.
64+
*
65+
* @tparam T value type of the allocator (the thing that it allocates)
66+
* @tparam Allocator the underlying allocator
67+
* @tparam syncness determines the synchronization of the limit
68+
*/
69+
template<typename T, template<typename> typename Allocator = std::allocator, limit_allocator_syncness syncness = limit_allocator_syncness::sync>
70+
struct limit_allocator {
71+
using control_block_type = detail_limit_allocator::limit_allocator_control_block<syncness>;
72+
using value_type = T;
73+
using upstream_allocator_type = Allocator<T>;
74+
using pointer = typename std::allocator_traits<upstream_allocator_type>::pointer;
75+
using const_pointer = typename std::allocator_traits<upstream_allocator_type>::const_pointer;
76+
using void_pointer = typename std::allocator_traits<upstream_allocator_type>::void_pointer;
77+
using const_void_pointer = typename std::allocator_traits<upstream_allocator_type>::const_void_pointer;
78+
using size_type = typename std::allocator_traits<upstream_allocator_type>::size_type;
79+
using difference_type = typename std::allocator_traits<upstream_allocator_type>::difference_type;
80+
81+
using propagate_on_container_copy_assignment = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_copy_assignment;
82+
using propagate_on_container_move_assignment = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_move_assignment;
83+
using propagate_on_container_swap = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_swap;
84+
using is_always_equal = std::false_type;
85+
86+
template<typename U>
87+
struct rebind {
88+
using other = limit_allocator<U, Allocator, syncness>;
89+
};
90+
91+
private:
92+
template<typename, template<typename> typename, limit_allocator_syncness>
93+
friend struct limit_allocator;
94+
95+
std::shared_ptr<control_block_type> control_block_;
96+
[[no_unique_address]] upstream_allocator_type inner_;
97+
98+
constexpr limit_allocator(std::shared_ptr<control_block_type> const &control_block, upstream_allocator_type const &alloc)
99+
requires(std::is_default_constructible_v<upstream_allocator_type>)
100+
: control_block_{control_block},
101+
inner_{alloc} {
102+
}
103+
104+
public:
105+
explicit constexpr limit_allocator(size_t bytes_limit)
106+
requires(std::is_default_constructible_v<upstream_allocator_type>)
107+
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
108+
inner_{} {
109+
}
110+
111+
constexpr limit_allocator(limit_allocator const &other) noexcept(std::is_nothrow_move_constructible_v<upstream_allocator_type>) = default;
112+
constexpr limit_allocator(limit_allocator &&other) noexcept(std::is_nothrow_copy_constructible_v<upstream_allocator_type>) = default;
113+
constexpr limit_allocator &operator=(limit_allocator const &other) noexcept(std::is_nothrow_copy_assignable_v<upstream_allocator_type>) = default;
114+
constexpr limit_allocator &operator=(limit_allocator &&other) noexcept(std::is_nothrow_move_assignable_v<upstream_allocator_type>) = default;
115+
constexpr ~limit_allocator() = default;
116+
117+
template<typename U>
118+
constexpr limit_allocator(limit_allocator<U, Allocator> const &other) noexcept(std::is_nothrow_constructible_v<upstream_allocator_type, typename limit_allocator<U, Allocator>::upstream_allocator_type const &>)
119+
: control_block_{other.control_block_},
120+
inner_{other.inner_} {
121+
}
122+
123+
constexpr limit_allocator(size_t bytes_limit, upstream_allocator_type const &upstream)
124+
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
125+
inner_{upstream} {
126+
}
127+
128+
constexpr limit_allocator(size_t bytes_limit, upstream_allocator_type &&upstream)
129+
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
130+
inner_{std::move(upstream)} {
131+
}
132+
133+
template<typename... Args>
134+
explicit constexpr limit_allocator(size_t bytes_limit, std::in_place_t, Args &&...args)
135+
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
136+
inner_{std::forward<Args>(args)...} {
137+
}
138+
139+
constexpr pointer allocate(size_t n) {
140+
control_block_->allocate(n * sizeof(T));
141+
142+
try {
143+
return std::allocator_traits<upstream_allocator_type>::allocate(inner_, n);
144+
} catch (...) {
145+
control_block_->deallocate(n * sizeof(T));
146+
throw;
147+
}
148+
}
149+
150+
constexpr void deallocate(pointer ptr, size_t n) {
151+
std::allocator_traits<upstream_allocator_type>::deallocate(inner_, ptr, n);
152+
control_block_->deallocate(n * sizeof(T));
153+
}
154+
155+
constexpr limit_allocator select_on_container_copy_construction() const {
156+
return limit_allocator{control_block_, std::allocator_traits<upstream_allocator_type>::select_on_container_copy_construction(inner_)};
157+
}
158+
159+
[[nodiscard]] upstream_allocator_type const &upstream_allocator() const noexcept {
160+
return inner_;
161+
}
162+
163+
friend constexpr void swap(limit_allocator &a, limit_allocator &b) noexcept(std::is_nothrow_swappable_v<upstream_allocator_type>)
164+
requires(std::is_swappable_v<upstream_allocator_type>)
165+
{
166+
using std::swap;
167+
swap(a.control_block_, b.control_block_);
168+
swap(a.inner_, b.inner_);
169+
}
170+
171+
bool operator==(limit_allocator const &other) const noexcept = default;
172+
bool operator!=(limit_allocator const &other) const noexcept = default;
173+
};
174+
}// namespace dice::template_library
175+
176+
#endif// DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP

include/dice/template-library/polymorphic_allocator.hpp

+5
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,11 @@ namespace dice::template_library {
337337
using propagate_on_container_swap = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_swap;
338338
using is_always_equal = typename std::allocator_traits<upstream_allocator_type>::is_always_equal;
339339

340+
template<typename U>
341+
struct rebind {
342+
using other = offset_ptr_stl_allocator<U, Allocator>;
343+
};
344+
340345
private:
341346
template<typename, template<typename> typename>
342347
friend struct offset_ptr_stl_allocator;

include/dice/template-library/variant2.hpp

+31-42
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@
1010
#include <utility>
1111
#include <variant>
1212

13+
#define DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(noexcept_spec, action_block) \
14+
if constexpr (noexcept_spec) { \
15+
action_block \
16+
} else { \
17+
try { \
18+
action_block \
19+
} catch (...) { \
20+
discriminant_ = discriminant_type::ValuelessByException; \
21+
throw; \
22+
} \
23+
}
24+
25+
1326
namespace dice::template_library {
1427
template<typename T, typename U>
1528
struct variant2;
@@ -314,13 +327,10 @@ namespace dice::template_library {
314327
break;
315328
}
316329
case discriminant_type::Second: {
317-
try {
318-
a_.~T();
330+
a_.~T();
331+
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_copy_constructible_v<U>, {
319332
new (&b_) U{other.b_};
320-
} catch (...) {
321-
discriminant_ = discriminant_type::ValuelessByException;
322-
throw;
323-
}
333+
});
324334
break;
325335
}
326336
case discriminant_type::ValuelessByException: {
@@ -337,13 +347,10 @@ namespace dice::template_library {
337347
case discriminant_type::Second: {
338348
switch (other.discriminant_) {
339349
case discriminant_type::First: {
340-
try {
341-
b_.~U();
350+
b_.~U();
351+
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_copy_constructible_v<T>, {
342352
new (&a_) T{other.a_};
343-
} catch (...) {
344-
discriminant_ = discriminant_type::ValuelessByException;
345-
throw;
346-
}
353+
});
347354
break;
348355
}
349356
case discriminant_type::Second: {
@@ -410,12 +417,9 @@ namespace dice::template_library {
410417
}
411418
case discriminant_type::Second: {
412419
a_.~T();
413-
try {
420+
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_move_constructible_v<U>, {
414421
new (&b_) U{std::move(other.b_)};
415-
} catch (...) {
416-
discriminant_ = discriminant_type::ValuelessByException;
417-
throw;
418-
}
422+
});
419423
break;
420424
}
421425
case discriminant_type::ValuelessByException: {
@@ -433,12 +437,9 @@ namespace dice::template_library {
433437
switch (other.discriminant_) {
434438
case discriminant_type::First: {
435439
b_.~U();
436-
try {
440+
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_move_constructible_v<T>, {
437441
new (&a_) T{std::move(other.a_)};
438-
} catch (...) {
439-
discriminant_ = discriminant_type::ValuelessByException;
440-
throw;
441-
}
442+
});
442443
break;
443444
}
444445
case discriminant_type::Second: {
@@ -499,12 +500,9 @@ namespace dice::template_library {
499500
}
500501
case discriminant_type::Second: {
501502
b_.~U();
502-
try {
503+
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_copy_constructible_v<T>, {
503504
new (&a_) T{value};
504-
} catch (...) {
505-
discriminant_ = discriminant_type::ValuelessByException;
506-
throw;
507-
}
505+
});
508506
discriminant_ = discriminant_type::First;
509507
break;
510508
}
@@ -533,12 +531,9 @@ namespace dice::template_library {
533531
}
534532
case discriminant_type::Second: {
535533
b_.~U();
536-
try {
534+
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_move_constructible_v<T>, {
537535
new (&a_) T{std::move(value)};
538-
} catch (...) {
539-
discriminant_ = discriminant_type::ValuelessByException;
540-
throw;
541-
}
536+
});
542537
discriminant_ = discriminant_type::First;
543538
break;
544539
}
@@ -562,12 +557,9 @@ namespace dice::template_library {
562557
switch (discriminant_) {
563558
case discriminant_type::First: {
564559
a_.~T();
565-
try {
560+
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_copy_constructible_v<U>, {
566561
new (&b_) U{value};
567-
} catch (...) {
568-
discriminant_ = discriminant_type::ValuelessByException;
569-
throw;
570-
}
562+
});
571563
discriminant_ = discriminant_type::Second;
572564
break;
573565
}
@@ -594,12 +586,9 @@ namespace dice::template_library {
594586
switch (discriminant_) {
595587
case discriminant_type::First: {
596588
a_.~T();
597-
try {
589+
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_move_constructible_v<U>, {
598590
new (&b_) U{std::move(value)};
599-
} catch (...) {
600-
discriminant_ = discriminant_type::ValuelessByException;
601-
throw;
602-
}
591+
});
603592
discriminant_ = discriminant_type::Second;
604593
break;
605594
}

0 commit comments

Comments
 (0)