Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions .github/workflows/c-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,27 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-22.04]
cxx: [g++-12, g++-11, clang++-15, clang++-14]
os: [ubuntu-22.04, ubuntu-24.04]
cxx: [g++-14, g++-13, g++-12, g++-11, clang++-18, clang++-17, clang++-16, clang++-15 ]
include:
- os: macos-latest
cxx: clang++
- os: ubuntu-24.04
cxx: g++-13
- os: ubuntu-24.04
exclude:
- os: ubuntu-22.04
cxx: clang++-16
- os: ubuntu-24.04
- os: ubuntu-22.04
cxx: clang++-17
- os: ubuntu-24.04
- os: ubuntu-22.04
cxx: clang++-18
- os: ubuntu-22.04
cxx: g++-13
- os: ubuntu-22.04
cxx: g++-14

- os: ubuntu-24.04
cxx: clang++-15
- os: ubuntu-24.04
cxx: g++-11

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

Expand Down
88 changes: 88 additions & 0 deletions tests/callable_tests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,91 @@ TEST_CASE("Callable Function lots of params") {
CHECK(func(10, 10.f, 'a', {}, {2, 'a', 1.2f, 1.4}) == 137);
CHECK(capture == 20);
}

TEST_CASE("FunctionSized") {
// This fails to compile (which is what we want)
// auto capture{0};
// auto capture2{0};
// auto capture3{0};
// auto func = Function<int(int, int)>{[&](int a, int b) {
// capture += 10;
// capture2 += 10;
// capture3 += 10;
// return a + b + capture;
// }};

auto capture{0};
auto capture2{0};
auto capture3{0};
auto func = FunctionSized<sizeof(void *) * 3, int(int, int)>{[&](int a, int b) {
capture += 10;
capture2 += 10;
capture3 += 10;
return a + b + capture;
}};
}

TEST_CASE("Callable") {
auto capture{0};

auto func = Callback{[&capture] {
capture += 10;
return capture;
}};

func();
CHECK(capture == 10);
func();
CHECK(capture == 20);
}

TEST_CASE("CallableSized") {
auto capture{0};
auto capture2{0};
auto capture3{0};

auto func = CallbackSized<sizeof(void *) * 3>{[&] {
capture += 10;
capture2 += 10;
capture3 += 10;
return capture;
}};

func();
CHECK(capture3 == 10);
func();
CHECK(capture3 == 20);
}

TEST_CASE("~Callable") {
auto capture{0};

Callback *func = new Callback{[&capture] {
capture += 10;
return capture;
}};

(*func)();
CHECK(capture == 10);
(*func)();
CHECK(capture == 20);

delete func;

Callback *empty = new Callback();
delete empty;
}

TEST_CASE("operator bool") {
auto capture{0};

Callback func;
CHECK_FALSE((bool)func);

func = Callback{[&capture] {
capture += 10;
return capture;
}};

CHECK((bool)func);
}
110 changes: 28 additions & 82 deletions util/callable.hh
Original file line number Diff line number Diff line change
Expand Up @@ -13,110 +13,48 @@
// (which shouldn't happen, since the point of this is to avoid the heap,
// but it could...)

template<unsigned BUFFER_SIZE = 2 * sizeof(void *)>
class CallbackSized {
private:
// static constexpr unsigned BUFFER_SIZE = 2 * sizeof(void *);

public:
CallbackSized() = default;

template<typename Callable>
CallbackSized(Callable callable) {
static_assert(sizeof(Callable) <= BUFFER_SIZE);
static_assert(std::is_invocable_v<Callable>);

new (&m_data[0]) Callable(callable);
m_callback = invoke<Callable>;
m_destroy = destroy<Callable>;
}

~CallbackSized() {
if (m_destroy)
m_destroy(&m_data[0]);
}

void call() {
// if (m_callback)
m_callback(&m_data[0]);
return;
}

void operator()() {
call();
}

operator bool() {
return m_callback;
}

private:
template<typename Callable>
static void invoke(void *object) {
Callable &callable = *reinterpret_cast<Callable *>(object);
callable();
}

template<typename Callable>
static void destroy(void *object) {
Callable &callable = *reinterpret_cast<Callable *>(object);
callable.~Callable();
}

private:
using CallbackM = void (*)(void *);
CallbackM m_callback{};

using Deleter = void (*)(void *);
Deleter m_destroy{};

alignas(uint64_t) uint8_t m_data[BUFFER_SIZE];
};

using Callback = CallbackSized<2 * sizeof(void *)>;

// Function<T> is a callback that takes parameters and returns something
template<typename Signature>
struct Function {};

template<typename Ret, typename... Args>
class Function<Ret(Args...)> {
private:
static constexpr uint8_t BUFFER_SIZE = 2 * sizeof(void *);
template<unsigned buffer_size, typename Signature>
struct FunctionSized {};

template<unsigned buffer_size, typename Ret, typename... Args>
class FunctionSized<buffer_size, Ret(Args...)> {
public:
Function() = default;
FunctionSized() = default;

template<typename Callable>
Function(Callable callable) {
static_assert(sizeof(Callable) <= BUFFER_SIZE);
static_assert(std::is_invocable_v<Callable, Args...>);

requires(sizeof(Callable) <= buffer_size && std::is_invocable_v<Callable, Args...>)
FunctionSized(Callable callable)
: m_callback{invoke<Callable>}
, m_destroy{destroy<Callable>} {
new (&m_data[0]) Callable(callable);
m_callback = invoke<Callable>;
m_destroy = destroy<Callable>;
}

~Function() {
~FunctionSized() {
if (m_destroy)
m_destroy(&m_data[0]);
}

Ret call(Args... args) {
if (m_callback)
return m_callback(&m_data[0], std::forward<Args>(args)...);
return Ret();
return m_callback(&m_data[0], std::forward<Args>(args)...);
}

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

operator bool() const {
return m_callback;
}

private:
template<typename Callable>
static Ret invoke(void *object, Args... args) {
Callable &callable = *reinterpret_cast<Callable *>(object);
return callable(std::forward<Args>(args)...);
if constexpr (std::is_same_v<void, Ret>)
callable(std::forward<Args>(args)...);
else
return callable(std::forward<Args>(args)...);
}

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

alignas(uint64_t) uint8_t m_data[BUFFER_SIZE];
alignas(uint64_t) uint8_t m_data[buffer_size];
};

template<typename Signature>
using Function = FunctionSized<sizeof(void *) * 2, Signature>;

template<unsigned size = sizeof(void *) * 2>
using CallbackSized = FunctionSized<size, void(void)>;

using Callback = CallbackSized<2 * sizeof(void *)>;