Skip to content

Commit 61313f0

Browse files
authored
Merge pull request #15 from 4ms/callable
callable.hh - Remove duplicate code
2 parents 340979d + 4498524 commit 61313f0

File tree

3 files changed

+131
-89
lines changed

3 files changed

+131
-89
lines changed

.github/workflows/c-cpp.yml

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,27 @@ jobs:
1111
build:
1212
strategy:
1313
matrix:
14-
os: [ubuntu-22.04]
15-
cxx: [g++-12, g++-11, clang++-15, clang++-14]
14+
os: [ubuntu-22.04, ubuntu-24.04]
15+
cxx: [g++-14, g++-13, g++-12, g++-11, clang++-18, clang++-17, clang++-16, clang++-15 ]
1616
include:
1717
- os: macos-latest
1818
cxx: clang++
19-
- os: ubuntu-24.04
20-
cxx: g++-13
21-
- os: ubuntu-24.04
19+
exclude:
20+
- os: ubuntu-22.04
2221
cxx: clang++-16
23-
- os: ubuntu-24.04
22+
- os: ubuntu-22.04
2423
cxx: clang++-17
25-
- os: ubuntu-24.04
24+
- os: ubuntu-22.04
2625
cxx: clang++-18
26+
- os: ubuntu-22.04
27+
cxx: g++-13
28+
- os: ubuntu-22.04
29+
cxx: g++-14
30+
31+
- os: ubuntu-24.04
32+
cxx: clang++-15
33+
- os: ubuntu-24.04
34+
cxx: g++-11
2735

2836
runs-on: ${{ matrix.os }}
2937

tests/callable_tests.cc

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,91 @@ TEST_CASE("Callable Function lots of params") {
4848
CHECK(func(10, 10.f, 'a', {}, {2, 'a', 1.2f, 1.4}) == 137);
4949
CHECK(capture == 20);
5050
}
51+
52+
TEST_CASE("FunctionSized") {
53+
// This fails to compile (which is what we want)
54+
// auto capture{0};
55+
// auto capture2{0};
56+
// auto capture3{0};
57+
// auto func = Function<int(int, int)>{[&](int a, int b) {
58+
// capture += 10;
59+
// capture2 += 10;
60+
// capture3 += 10;
61+
// return a + b + capture;
62+
// }};
63+
64+
auto capture{0};
65+
auto capture2{0};
66+
auto capture3{0};
67+
auto func = FunctionSized<sizeof(void *) * 3, int(int, int)>{[&](int a, int b) {
68+
capture += 10;
69+
capture2 += 10;
70+
capture3 += 10;
71+
return a + b + capture;
72+
}};
73+
}
74+
75+
TEST_CASE("Callable") {
76+
auto capture{0};
77+
78+
auto func = Callback{[&capture] {
79+
capture += 10;
80+
return capture;
81+
}};
82+
83+
func();
84+
CHECK(capture == 10);
85+
func();
86+
CHECK(capture == 20);
87+
}
88+
89+
TEST_CASE("CallableSized") {
90+
auto capture{0};
91+
auto capture2{0};
92+
auto capture3{0};
93+
94+
auto func = CallbackSized<sizeof(void *) * 3>{[&] {
95+
capture += 10;
96+
capture2 += 10;
97+
capture3 += 10;
98+
return capture;
99+
}};
100+
101+
func();
102+
CHECK(capture3 == 10);
103+
func();
104+
CHECK(capture3 == 20);
105+
}
106+
107+
TEST_CASE("~Callable") {
108+
auto capture{0};
109+
110+
Callback *func = new Callback{[&capture] {
111+
capture += 10;
112+
return capture;
113+
}};
114+
115+
(*func)();
116+
CHECK(capture == 10);
117+
(*func)();
118+
CHECK(capture == 20);
119+
120+
delete func;
121+
122+
Callback *empty = new Callback();
123+
delete empty;
124+
}
125+
126+
TEST_CASE("operator bool") {
127+
auto capture{0};
128+
129+
Callback func;
130+
CHECK_FALSE((bool)func);
131+
132+
func = Callback{[&capture] {
133+
capture += 10;
134+
return capture;
135+
}};
136+
137+
CHECK((bool)func);
138+
}

util/callable.hh

Lines changed: 28 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -13,110 +13,48 @@
1313
// (which shouldn't happen, since the point of this is to avoid the heap,
1414
// but it could...)
1515

16-
template<unsigned BUFFER_SIZE = 2 * sizeof(void *)>
17-
class CallbackSized {
18-
private:
19-
// static constexpr unsigned BUFFER_SIZE = 2 * sizeof(void *);
20-
21-
public:
22-
CallbackSized() = default;
23-
24-
template<typename Callable>
25-
CallbackSized(Callable callable) {
26-
static_assert(sizeof(Callable) <= BUFFER_SIZE);
27-
static_assert(std::is_invocable_v<Callable>);
28-
29-
new (&m_data[0]) Callable(callable);
30-
m_callback = invoke<Callable>;
31-
m_destroy = destroy<Callable>;
32-
}
33-
34-
~CallbackSized() {
35-
if (m_destroy)
36-
m_destroy(&m_data[0]);
37-
}
38-
39-
void call() {
40-
// if (m_callback)
41-
m_callback(&m_data[0]);
42-
return;
43-
}
44-
45-
void operator()() {
46-
call();
47-
}
48-
49-
operator bool() {
50-
return m_callback;
51-
}
52-
53-
private:
54-
template<typename Callable>
55-
static void invoke(void *object) {
56-
Callable &callable = *reinterpret_cast<Callable *>(object);
57-
callable();
58-
}
59-
60-
template<typename Callable>
61-
static void destroy(void *object) {
62-
Callable &callable = *reinterpret_cast<Callable *>(object);
63-
callable.~Callable();
64-
}
65-
66-
private:
67-
using CallbackM = void (*)(void *);
68-
CallbackM m_callback{};
69-
70-
using Deleter = void (*)(void *);
71-
Deleter m_destroy{};
72-
73-
alignas(uint64_t) uint8_t m_data[BUFFER_SIZE];
74-
};
75-
76-
using Callback = CallbackSized<2 * sizeof(void *)>;
77-
7816
// Function<T> is a callback that takes parameters and returns something
79-
template<typename Signature>
80-
struct Function {};
81-
82-
template<typename Ret, typename... Args>
83-
class Function<Ret(Args...)> {
84-
private:
85-
static constexpr uint8_t BUFFER_SIZE = 2 * sizeof(void *);
17+
template<unsigned buffer_size, typename Signature>
18+
struct FunctionSized {};
8619

20+
template<unsigned buffer_size, typename Ret, typename... Args>
21+
class FunctionSized<buffer_size, Ret(Args...)> {
8722
public:
88-
Function() = default;
23+
FunctionSized() = default;
8924

9025
template<typename Callable>
91-
Function(Callable callable) {
92-
static_assert(sizeof(Callable) <= BUFFER_SIZE);
93-
static_assert(std::is_invocable_v<Callable, Args...>);
94-
26+
requires(sizeof(Callable) <= buffer_size && std::is_invocable_v<Callable, Args...>)
27+
FunctionSized(Callable callable)
28+
: m_callback{invoke<Callable>}
29+
, m_destroy{destroy<Callable>} {
9530
new (&m_data[0]) Callable(callable);
96-
m_callback = invoke<Callable>;
97-
m_destroy = destroy<Callable>;
9831
}
9932

100-
~Function() {
33+
~FunctionSized() {
10134
if (m_destroy)
10235
m_destroy(&m_data[0]);
10336
}
10437

10538
Ret call(Args... args) {
106-
if (m_callback)
107-
return m_callback(&m_data[0], std::forward<Args>(args)...);
108-
return Ret();
39+
return m_callback(&m_data[0], std::forward<Args>(args)...);
10940
}
11041

11142
Ret operator()(Args... args) {
11243
return call(std::forward<Args>(args)...);
11344
}
11445

46+
operator bool() const {
47+
return m_callback;
48+
}
49+
11550
private:
11651
template<typename Callable>
11752
static Ret invoke(void *object, Args... args) {
11853
Callable &callable = *reinterpret_cast<Callable *>(object);
119-
return callable(std::forward<Args>(args)...);
54+
if constexpr (std::is_same_v<void, Ret>)
55+
callable(std::forward<Args>(args)...);
56+
else
57+
return callable(std::forward<Args>(args)...);
12058
}
12159

12260
template<typename Callable>
@@ -132,5 +70,13 @@ private:
13270
using Deleter = void (*)(void *);
13371
Deleter m_destroy{};
13472

135-
alignas(uint64_t) uint8_t m_data[BUFFER_SIZE];
73+
alignas(uint64_t) uint8_t m_data[buffer_size];
13674
};
75+
76+
template<typename Signature>
77+
using Function = FunctionSized<sizeof(void *) * 2, Signature>;
78+
79+
template<unsigned size = sizeof(void *) * 2>
80+
using CallbackSized = FunctionSized<size, void(void)>;
81+
82+
using Callback = CallbackSized<2 * sizeof(void *)>;

0 commit comments

Comments
 (0)