diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 8ab8512..d3b428b 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -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 }} diff --git a/tests/callable_tests.cc b/tests/callable_tests.cc index a726b0b..860e7b7 100644 --- a/tests/callable_tests.cc +++ b/tests/callable_tests.cc @@ -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 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{[&](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{[&] { + 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); +} diff --git a/util/callable.hh b/util/callable.hh index 91b4431..7648757 100644 --- a/util/callable.hh +++ b/util/callable.hh @@ -13,110 +13,48 @@ // (which shouldn't happen, since the point of this is to avoid the heap, // but it could...) -template -class CallbackSized { -private: - // static constexpr unsigned BUFFER_SIZE = 2 * sizeof(void *); - -public: - CallbackSized() = default; - - template - CallbackSized(Callable callable) { - static_assert(sizeof(Callable) <= BUFFER_SIZE); - static_assert(std::is_invocable_v); - - new (&m_data[0]) Callable(callable); - m_callback = invoke; - m_destroy = destroy; - } - - ~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 - static void invoke(void *object) { - Callable &callable = *reinterpret_cast(object); - callable(); - } - - template - static void destroy(void *object) { - Callable &callable = *reinterpret_cast(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 is a callback that takes parameters and returns something -template -struct Function {}; - -template -class Function { -private: - static constexpr uint8_t BUFFER_SIZE = 2 * sizeof(void *); +template +struct FunctionSized {}; +template +class FunctionSized { public: - Function() = default; + FunctionSized() = default; template - Function(Callable callable) { - static_assert(sizeof(Callable) <= BUFFER_SIZE); - static_assert(std::is_invocable_v); - + requires(sizeof(Callable) <= buffer_size && std::is_invocable_v) + FunctionSized(Callable callable) + : m_callback{invoke} + , m_destroy{destroy} { new (&m_data[0]) Callable(callable); - m_callback = invoke; - m_destroy = destroy; } - ~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)...); - return Ret(); + return m_callback(&m_data[0], std::forward(args)...); } Ret operator()(Args... args) { return call(std::forward(args)...); } + operator bool() const { + return m_callback; + } + private: template static Ret invoke(void *object, Args... args) { Callable &callable = *reinterpret_cast(object); - return callable(std::forward(args)...); + if constexpr (std::is_same_v) + callable(std::forward(args)...); + else + return callable(std::forward(args)...); } template @@ -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 +using Function = FunctionSized; + +template +using CallbackSized = FunctionSized; + +using Callback = CallbackSized<2 * sizeof(void *)>;