Skip to content

Commit dd33a58

Browse files
authored
Merge branch 'develop' into feature/fmt_join
2 parents 3a77316 + 160923f commit dd33a58

8 files changed

Lines changed: 271 additions & 24 deletions

File tree

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ It contains:
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
1212
- `limit_allocator`: Allocator wrapper that limits the amount of memory that is allowed to be allocated
13+
- `DICE_MEMFN`: Macro to pass member functions like free functions as argument.
1314
- `pool` & `pool_allocator`: Arena/pool allocator optimized for a limited number of known allocation sizes.
1415
- `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)
15-
- `overloaded`: Composition for `std::variant` visitor lambdas
16+
- `overloaded` and `match`: Batteries for `std::variant` (and also `dtl::variant2`. Compose re-usable visitors with `overload` or apply a single-use visitor directly with `match`.
1617
- `flex_array`: A combination of `std::array`, `std::span` and a `vector` with small buffer optimization
1718
- `tuple_algorithms`: Some algorithms for iterating tuples
1819
- `fmt_join`: A helper to join elements of a range with a separator for use with `std::format` alike [fmt::join](https://fmt.dev/latest/api/#range-and-tuple-formatting)
@@ -73,6 +74,11 @@ Which means: vtables will not work (because they use absolute pointers) and ther
7374
Allocator wrapper that limits the amount of memory that can be allocated through the inner allocator.
7475
If the limit is exceeded it will throw `std::bad_alloc`.
7576

77+
### `DICE_MEMFN`
78+
DICE_MEMFN is a convenience macro that makes it easy to pass member functions as argument, e.g., to range adaptors.
79+
It eliminates boilerplate code by creating a lambda that captures this and perfectly forwards
80+
arguments to your member function.
81+
7682
### `pool_allocator`
7783
A memory arena/pool allocator with configurable allocation sizes. This is implemented
7884
as a collection of pools with varying allocation sizes. Allocations that do not
@@ -83,7 +89,7 @@ A mechanism similar to go's `defer` keyword, which can be used to defer some act
8389
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).
8490
Usage examples can be found [here](examples/examples_defer.cpp).
8591

86-
### `tuple algorthims`
92+
### `tuple algorithms`
8793
Some algorithms for iterating tuples, for example `tuple_fold` a fold/reduce implementation for tuples.
8894

8995
### `fmt_join`
@@ -109,6 +115,9 @@ Like `std::variant` but specifically optimized for usage with two types/variants
109115
The internal representation is a `union` of the two types plus a 1 byte (3 state) discriminant.
110116
Additionally, `visit` does not involve any virtual function calls.
111117

118+
### `overload` / `match`
119+
Things that are missing around `std::variant` and visitors in the standard library. Implementation of the common `overload` pattern to create re-usable visitors. Also, comes with a `match` function that allows you to declare the visitor directly inline when applying it.
120+
112121
### `type_traits.hpp`
113122
Things that are missing in the standard library `<type_traits>` header.
114123

examples/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ target_link_libraries(example_limit_allocator
9494
dice-template-library::dice-template-library
9595
)
9696

97+
add_executable(example_memfn
98+
example_memfn.cpp)
99+
target_link_libraries(example_memfn
100+
PRIVATE
101+
dice-template-library::dice-template-library
102+
)
103+
97104
add_executable(example_pool_allocator
98105
example_pool_allocator.cpp)
99106
target_link_libraries(example_pool_allocator

examples/example_memfn.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include <algorithm>
2+
#include <dice/template-library/memfn.hpp>
3+
4+
#include <cassert>
5+
#include <ranges>
6+
#include <string>
7+
#include <vector>
8+
9+
struct NumberProcessor {
10+
std::vector<int> numbers_ = {1, 2, 3, 4, 5, 6, 7, 8};
11+
std::string prefix_ = "item-";
12+
int divisor_ = 2;
13+
14+
[[nodiscard]] bool has_divisor(int n) const { return n % divisor_ == 0; }
15+
16+
[[nodiscard]] std::string decorate(int n) const { return prefix_ + std::to_string(n); }
17+
18+
void example() {
19+
auto processed_view = numbers_ |
20+
std::views::filter(DICE_MEMFN(has_divisor)) |
21+
std::views::transform(DICE_MEMFN(decorate));
22+
23+
assert(std::ranges::equal(processed_view, std::vector<std::string>{"item-2", "item-4", "item-6", "item-8"}));
24+
}
25+
};
26+
27+
int main() {
28+
NumberProcessor processor;
29+
processor.example();
30+
return 0;
31+
}

examples/example_variant2.cpp

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include <cassert>
22
#include <string>
3+
#include <variant>
34

5+
#include <dice/template-library/overloaded.hpp>
46
#include <dice/template-library/variant2.hpp>
57

68
namespace dtl = dice::template_library;
@@ -12,30 +14,36 @@ int main() {
1214
dtl::variant2<int, float> v;
1315
dtl::variant2<int, float> w;
1416

15-
v = 42; // v contains int
17+
v = 42;// v contains int
1618
int i = dtl::get<int>(v);
17-
assert(42 == i); // succeeds
19+
assert(42 == i);// succeeds
1820
w = dtl::get<int>(v);
19-
w = dtl::get<0>(v); // same effect as the previous line
20-
w = v; // same effect as the previous line
21+
w = dtl::get<0>(v);// same effect as the previous line
22+
w = v; // same effect as the previous line
2123

2224
// dtl::get<double>(v); // error: no double in [int, float]
2325
// dtl::get<3>(v); // error: valid index values are 0 and 1
2426

2527
try {
26-
(void) dtl::get<float>(w); // w contains int, not float: will throw
27-
}
28-
catch (dtl::bad_variant_access const &ex) {
28+
(void) dtl::get<float>(w);// w contains int, not float: will throw
29+
} catch (dtl::bad_variant_access const &ex) {
2930
assert(ex.what() == "bad variant access"s);
3031
}
3132

3233
dtl::variant<std::string> x("abc");
3334
// converting constructors work when unambiguous
34-
x = "def"; // converting assignment also works when unambiguous
35+
x = "def";// converting assignment also works when unambiguous
3536

36-
dtl::variant<std::string, void const*> y("abc");
37+
dtl::variant<std::string, void const *> y("abc");
3738
// casts to void const* when passed a char const*
38-
assert(dtl::holds_alternative<void const*>(y)); // succeeds
39+
assert(dtl::holds_alternative<void const *>(y));// succeeds
3940
y = "xyz"s;
40-
assert(dtl::holds_alternative<std::string>(y)); // succeeds
41+
assert(dtl::holds_alternative<std::string>(y));// succeeds
42+
43+
auto visitor = dtl::overloaded{
44+
[](int x) { return x; },
45+
[](double d) { return static_cast<int>(d); }};
46+
47+
auto z = dtl::visit(visitor, v);
48+
assert(z == 42);
4149
}

examples/examples_overloaded.cpp

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
#include <dice/template-library/overloaded.hpp>
2+
#include <dice/template-library/variant2.hpp>
23

34
#include <cassert>
45
#include <string>
56
#include <variant>
67

8+
namespace dtl = dice::template_library;
9+
10+
711
int main() {
8-
auto visitor = dice::template_library::overloaded{
9-
[](int i) {
10-
return "Got an int: " + std::to_string(i);
11-
},
12-
[](std::string s) {
13-
return "Got a string: " + s;
14-
},
15-
[]([[maybe_unused]] auto u) {
16-
return std::string{"Got something else"};
17-
}
18-
};
12+
auto visitor = dtl::overloaded{
13+
[](int i) {
14+
return "Got an int: " + std::to_string(i);
15+
},
16+
[](std::string s) {
17+
return "Got a string: " + s;
18+
},
19+
[]([[maybe_unused]] auto u) {
20+
return std::string{"Got something else"};
21+
}};
1922

2023
std::variant<std::string, int, double> v = "Hello World";
2124
auto r1 = std::visit(visitor, v);
@@ -28,4 +31,25 @@ int main() {
2831
v = 5.0;
2932
auto r3 = std::visit(visitor, v);
3033
assert(r3 == "Got something else");
34+
35+
// Using match for single-use visitors
36+
auto r4 = dtl::match(
37+
v,
38+
[](int x) { return x; },
39+
[](double d) { return static_cast<int>(d); },
40+
[]([[maybe_unused]] auto f) { return 0; });
41+
assert(r4 == 5);
42+
43+
// also works with variant2
44+
dtl::variant2<int, double> v2 = 42.3;
45+
auto r5 = dtl::visit(visitor, v2);
46+
assert(r5 == "Got something else");
47+
48+
auto r6 = dice::template_library::match(
49+
v2,
50+
[](int x) { return x; },
51+
[](double d) { return static_cast<int>(d); },
52+
[]([[maybe_unused]] auto f) { return 0; });
53+
assert(r6 == 42);
54+
return 0;
3155
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#ifndef DICE_TEMPLATE_LIBRARY_MEMFN_HPP
2+
#define DICE_TEMPLATE_LIBRARY_MEMFN_HPP
3+
4+
#include <utility>
5+
6+
/**
7+
* @brief Creates a lambda that calls a member function on the current object. Arguments, if any are required, are forwarded.
8+
*
9+
* @param member_func The name of the member function to call. They are captured from the context.
10+
* @param ...args A list of arguments to pass to the call. These bind to the arguments at the front.
11+
* @param ...runtime_args Some functions, like range_adaptors, pass in an element. This is used to capture that. Binds to arguments after ...args
12+
*
13+
*/
14+
#define DICE_MEMFN(member_func, ...) \
15+
([&, this]<typename... RuntimeArgs>(RuntimeArgs &&...runtime_args) -> decltype(auto) { \
16+
return this->member_func(__VA_ARGS__ __VA_OPT__(, ) std::forward<RuntimeArgs>(runtime_args)...); \
17+
})
18+
#endif// DICE_TEMPLATE_LIBRARY_MEMFN_HPP

tests/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ custom_add_test(tests_type_traits)
7070
add_executable(tests_limit_allocator tests_limit_allocator.cpp)
7171
custom_add_test(tests_limit_allocator)
7272

73+
add_executable(tests_memfn tests_memfn.cpp)
74+
custom_add_test(tests_memfn)
75+
7376
add_executable(tests_pool_allocator tests_pool_allocator.cpp)
7477
custom_add_test(tests_pool_allocator)
7578

tests/tests_memfn.cpp

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
2+
#include <doctest/doctest.h>
3+
4+
#include <dice/template-library/memfn.hpp>
5+
6+
#include <algorithm>
7+
#include <ranges>
8+
#include <string>
9+
#include <utility>
10+
#include <vector>
11+
12+
TEST_SUITE("DICE_MEMFN Argument Forwarding") {
13+
14+
struct AbstractFixture {
15+
enum struct CallSignature : uint8_t {
16+
None,
17+
ByValue,
18+
ByConstLvalueRef,
19+
ByLvalueRef,
20+
ByRvalueRef,
21+
MixedArgs,
22+
RangeTransform
23+
};
24+
CallSignature last_call = CallSignature::None;
25+
std::string moved_in_data;
26+
27+
// Member functions to be called by the macro
28+
void process_by_value(std::string s) { last_call = CallSignature::ByValue; }
29+
void process_by_const_lvalue_ref(std::string const &s) { last_call = CallSignature::ByConstLvalueRef; }
30+
void process_by_lvalue_ref(std::string &s) {
31+
last_call = CallSignature::ByLvalueRef;
32+
s += "_modified";
33+
}
34+
void process_by_rvalue_ref(std::string &&s) {
35+
last_call = CallSignature::ByRvalueRef;
36+
moved_in_data = std::move(s);
37+
}
38+
void process_mixed_args(std::string const &s_value, std::string const &s_const_ref, int &i_ref) {
39+
last_call = CallSignature::MixedArgs;
40+
i_ref *= 2;
41+
moved_in_data = s_const_ref + "_" + s_value;
42+
}
43+
std::string transform_int_to_string(std::string const &prefix, int i) {
44+
last_call = CallSignature::RangeTransform;
45+
return prefix + std::to_string(i);
46+
}
47+
48+
49+
// methods for the tests
50+
void run_const_lvalue_ref_test() {
51+
std::string const data = "immutable";
52+
auto task = DICE_MEMFN(process_by_const_lvalue_ref);
53+
task(data);
54+
CHECK(last_call == CallSignature::ByConstLvalueRef);
55+
}
56+
57+
void run_lvalue_ref_test() {
58+
std::string data = "mutable";
59+
auto task = DICE_MEMFN(process_by_lvalue_ref);
60+
task(data);
61+
CHECK(last_call == CallSignature::ByLvalueRef);
62+
CHECK(data == "mutable_modified");
63+
}
64+
65+
void run_rvalue_ref_test() {
66+
std::string data = "movable";
67+
auto task = DICE_MEMFN(process_by_rvalue_ref);
68+
task(std::move(data));
69+
CHECK(last_call == CallSignature::ByRvalueRef);
70+
CHECK(moved_in_data == "movable");
71+
CHECK(data.empty());
72+
}
73+
74+
void run_by_value_test() {
75+
std::string data = "copyable";
76+
auto task = DICE_MEMFN(process_by_value);
77+
78+
task(data);
79+
CHECK(last_call == CallSignature::ByValue);
80+
CHECK(data == "copyable");
81+
82+
task("temporary");
83+
CHECK(last_call == CallSignature::ByValue);
84+
}
85+
86+
void run_mixed_args_test() {
87+
std::string const const_data = "hello";
88+
int mutable_int = 10;
89+
std::string movable_data = "world";
90+
91+
auto task = DICE_MEMFN(process_mixed_args, movable_data);
92+
task(const_data, mutable_int);
93+
94+
CHECK(last_call == CallSignature::MixedArgs);
95+
CHECK(mutable_int == 20);
96+
CHECK(moved_in_data == "hello_world");
97+
CHECK_FALSE(movable_data.empty());
98+
}
99+
100+
void run_range_adaptor_test() {
101+
std::vector<int> source_data = {10, 20, 30};
102+
std::string const prefix = "item_";
103+
104+
auto transformed_view = source_data | std::views::transform(DICE_MEMFN(transform_int_to_string, prefix));
105+
106+
std::vector<std::string> results;
107+
std::ranges::copy(transformed_view, std::back_inserter(results));
108+
109+
CHECK(last_call == CallSignature::RangeTransform);
110+
111+
CHECK(results.size() == 3);
112+
CHECK(results[0] == "item_10");
113+
CHECK(results[1] == "item_20");
114+
CHECK(results[2] == "item_30");
115+
}
116+
};
117+
118+
TEST_CASE("Forwards arguments as const lvalue references") {
119+
AbstractFixture f;
120+
f.run_const_lvalue_ref_test();
121+
}
122+
123+
TEST_CASE("Forwards arguments as non-const lvalue references") {
124+
AbstractFixture f;
125+
f.run_lvalue_ref_test();
126+
}
127+
128+
TEST_CASE("Forwards arguments as rvalue references") {
129+
AbstractFixture f;
130+
f.run_rvalue_ref_test();
131+
}
132+
133+
TEST_CASE("Forwards arguments by value") {
134+
AbstractFixture f;
135+
f.run_by_value_test();
136+
}
137+
138+
TEST_CASE("Forwards mixed arguments with pre-binding") {
139+
AbstractFixture f;
140+
f.run_mixed_args_test();
141+
}
142+
143+
TEST_CASE("Usage with a range adaptor") {
144+
AbstractFixture f;
145+
f.run_range_adaptor_test();
146+
}
147+
}

0 commit comments

Comments
 (0)