From 6845d2531d475195df7c88ac4224870a3c6b5c9a Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 8 Dec 2023 12:12:17 +0100 Subject: [PATCH 1/5] Restore s11n capabilities for empty event and step callbacks. --- CMakeLists.txt | 1 + include/heyoka/callable.hpp | 58 +++++++++++++------- include/heyoka/step_callback.hpp | 24 ++++++++- include/heyoka/taylor.hpp | 35 ++++++++++++ src/detail/empty_callable_s11n.cpp | 86 ++++++++++++++++++++++++++++++ test/callable.cpp | 32 +++++++++-- test/step_callback.cpp | 50 ++++++++++++++++- 7 files changed, 263 insertions(+), 23 deletions(-) create mode 100644 src/detail/empty_callable_s11n.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index caea1a625..3760423cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,6 +178,7 @@ set(HEYOKA_SRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/detail/div.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/detail/sub.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/detail/vector_math.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/detail/empty_callable_s11n.cpp" # NOTE: this will be an empty file in case we are not # building with support for real. "${CMAKE_CURRENT_SOURCE_DIR}/src/detail/real_helpers.cpp" diff --git a/include/heyoka/callable.hpp b/include/heyoka/callable.hpp index 914118b00..36a45d4a2 100644 --- a/include/heyoka/callable.hpp +++ b/include/heyoka/callable.hpp @@ -37,6 +37,18 @@ struct is_any_callable; namespace detail { +// An empty struct used in the default initialisation +// of callable objects. +// NOTE: we use this rather than, e.g., a null function +// pointer so that we can enable serialisation of +// default-constructed callables. +struct HEYOKA_DLL_PUBLIC_INLINE_CLASS empty_callable { + template + void serialize(Archive &, unsigned) + { + } +}; + // Declaration of the callable interface template. template struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface { @@ -60,19 +72,24 @@ template && std::copy_constructible struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface : virtual callable_iface, tanuki::iface_impl_helper { - R operator()(Args... args) final + explicit operator bool() const noexcept final { using unrefT = std::remove_reference_t>; if constexpr (std::is_pointer_v || std::is_member_pointer_v) { - if (this->value() == nullptr) { - throw std::bad_function_call{}; - } + return this->value() != nullptr; + } else if constexpr (is_any_callable::value || is_any_std_func_v) { + return static_cast(this->value()); + } else { + return true; + } + } + R operator()(Args... args) final + { + // Check if this is empty before invoking the call operator. + if (!this->operator bool()) { + throw std::bad_function_call{}; } - - // NOTE: if this->value() is an empty std::function or callable, - // the std::bad_function_call exception will be raised - // by the invocation. if constexpr (std::is_same_v) { static_cast(std::invoke(this->value(), std::forward(args)...)); @@ -80,17 +97,20 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface return std::invoke(this->value(), std::forward(args)...); } } +}; + +// Implementation of the callable interface for the empty callable. +template + requires std::same_as +struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface + : virtual callable_iface { explicit operator bool() const noexcept final { - using unrefT = std::remove_reference_t>; - - if constexpr (std::is_pointer_v || std::is_member_pointer_v) { - return this->value() != nullptr; - } else if constexpr (is_any_callable::value || is_any_std_func_v) { - return static_cast(this->value()); - } else { - return true; - } + return false; + } + [[noreturn]] R operator()(Args...) final + { + throw std::bad_function_call{}; } }; @@ -103,6 +123,7 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_ref_iface_impl { auto operator()(FArgs &&...fargs) -> decltype(iface_ptr(*static_cast(this))->operator()(std::forward(fargs)...)) { + // NOTE: a wrap in invalid state is considered empty. if (is_invalid(*static_cast(this))) { throw std::bad_function_call{}; } @@ -112,6 +133,7 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_ref_iface_impl { explicit operator bool() const noexcept { + // NOTE: a wrap in invalid state is considered empty. if (is_invalid(*static_cast(this))) { return false; } else { @@ -147,7 +169,7 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_ref_iface { // Definition of the callable wrap. template using callable_wrap_t = tanuki::wrap::template type>{ + tanuki::config::template type>{ // Similarly to std::function, ensure that callable can store // in static storage pointers and reference wrappers. // NOTE: reference wrappers are not guaranteed to have the size diff --git a/include/heyoka/step_callback.hpp b/include/heyoka/step_callback.hpp index 1544877b6..c9e1cbbd7 100644 --- a/include/heyoka/step_callback.hpp +++ b/include/heyoka/step_callback.hpp @@ -100,7 +100,7 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS step_cb_ifaceT { // Configuration. template -inline constexpr auto step_cb_wrap_config = tanuki::config::template type>{ +inline constexpr auto step_cb_wrap_config = tanuki::config::template type>{ // Similarly to std::function, ensure that callable can store // in static storage pointers and reference wrappers. // NOTE: reference wrappers are not guaranteed to have the size @@ -264,4 +264,26 @@ HEYOKA_S11N_STEP_CALLBACK_EXPORT_KEY(heyoka::step_callback_set, mppp #endif +// Export the s11n keys for default-constructed step callbacks. +HEYOKA_S11N_STEP_CALLBACK_EXPORT_KEY(heyoka::detail::empty_callable, float) +HEYOKA_S11N_STEP_CALLBACK_EXPORT_KEY(heyoka::detail::empty_callable, double) +HEYOKA_S11N_STEP_CALLBACK_EXPORT_KEY(heyoka::detail::empty_callable, long double) + +HEYOKA_S11N_STEP_CALLBACK_BATCH_EXPORT_KEY(heyoka::detail::empty_callable, float) +HEYOKA_S11N_STEP_CALLBACK_BATCH_EXPORT_KEY(heyoka::detail::empty_callable, double) +HEYOKA_S11N_STEP_CALLBACK_BATCH_EXPORT_KEY(heyoka::detail::empty_callable, long double) + +#if defined(HEYOKA_HAVE_REAL128) + +HEYOKA_S11N_STEP_CALLBACK_EXPORT_KEY(heyoka::detail::empty_callable, mppp::real128) +HEYOKA_S11N_STEP_CALLBACK_BATCH_EXPORT_KEY(heyoka::detail::empty_callable, mppp::real128) + +#endif + +#if defined(HEYOKA_HAVE_REAL) + +HEYOKA_S11N_STEP_CALLBACK_EXPORT_KEY(heyoka::detail::empty_callable, mppp::real) + +#endif + #endif diff --git a/include/heyoka/taylor.hpp b/include/heyoka/taylor.hpp index 1ec3a64f5..0a294fa76 100644 --- a/include/heyoka/taylor.hpp +++ b/include/heyoka/taylor.hpp @@ -2006,4 +2006,39 @@ BOOST_CLASS_VERSION(heyoka::taylor_adaptive_batch, heyoka::detail::t #endif +// Export the s11n keys for default-constructed event callbacks. +#define HEYOKA_S11N_EVENT_CALLBACKS_EXPORT_KEY(T) \ + HEYOKA_S11N_CALLABLE_EXPORT_KEY(heyoka::detail::empty_callable, void, heyoka::taylor_adaptive &, T, int) \ + HEYOKA_S11N_CALLABLE_EXPORT_KEY(heyoka::detail::empty_callable, bool, heyoka::taylor_adaptive &, bool, int) + +#define HEYOKA_S11N_BATCH_EVENT_CALLBACKS_EXPORT_KEY(T) \ + HEYOKA_S11N_CALLABLE_EXPORT_KEY(heyoka::detail::empty_callable, void, heyoka::taylor_adaptive_batch &, T, int, \ + std::uint32_t) \ + HEYOKA_S11N_CALLABLE_EXPORT_KEY(heyoka::detail::empty_callable, bool, heyoka::taylor_adaptive_batch &, bool, \ + int, std::uint32_t) + +HEYOKA_S11N_EVENT_CALLBACKS_EXPORT_KEY(float) +HEYOKA_S11N_EVENT_CALLBACKS_EXPORT_KEY(double) +HEYOKA_S11N_EVENT_CALLBACKS_EXPORT_KEY(long double) + +HEYOKA_S11N_BATCH_EVENT_CALLBACKS_EXPORT_KEY(float) +HEYOKA_S11N_BATCH_EVENT_CALLBACKS_EXPORT_KEY(double) +HEYOKA_S11N_BATCH_EVENT_CALLBACKS_EXPORT_KEY(long double) + +#if defined(HEYOKA_HAVE_REAL128) + +HEYOKA_S11N_EVENT_CALLBACKS_EXPORT_KEY(mppp::real128) +HEYOKA_S11N_BATCH_EVENT_CALLBACKS_EXPORT_KEY(mppp::real128) + +#endif + +#if defined(HEYOKA_HAVE_REAL) + +HEYOKA_S11N_EVENT_CALLBACKS_EXPORT_KEY(mppp::real) + +#endif + +#undef HEYOKA_S11N_EVENT_CALLBACKS_EXPORT_KEY +#undef HEYOKA_S11N_BATCH_EVENT_CALLBACKS_EXPORT_KEY + #endif diff --git a/src/detail/empty_callable_s11n.cpp b/src/detail/empty_callable_s11n.cpp new file mode 100644 index 000000000..f2c01faa0 --- /dev/null +++ b/src/detail/empty_callable_s11n.cpp @@ -0,0 +1,86 @@ +// Copyright 2020, 2021, 2022, 2023 Francesco Biscani (bluescarni@gmail.com), Dario Izzo (dario.izzo@gmail.com) +// +// This file is part of the heyoka library. +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include + +#if defined(HEYOKA_HAVE_REAL128) + +#include + +#endif + +#if defined(HEYOKA_HAVE_REAL) + +#include + +#endif + +// NOTE: this file contains the code for registering default-constructed +// event and step callbacks in the serialisation system. + +// Scalar and batch event callbacks. +#define HEYOKA_S11N_IMPLEMENT_EVENT_CALLBACKS(T) \ + HEYOKA_S11N_CALLABLE_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, void, heyoka::taylor_adaptive &, T, int) \ + HEYOKA_S11N_CALLABLE_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, bool, heyoka::taylor_adaptive &, bool, int) + +#define HEYOKA_S11N_IMPLEMENT_BATCH_EVENT_CALLBACKS(T) \ + HEYOKA_S11N_CALLABLE_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, void, heyoka::taylor_adaptive_batch &, T, \ + int, std::uint32_t) \ + HEYOKA_S11N_CALLABLE_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, bool, heyoka::taylor_adaptive_batch &, \ + bool, int, std::uint32_t) + +HEYOKA_S11N_IMPLEMENT_EVENT_CALLBACKS(float) +HEYOKA_S11N_IMPLEMENT_EVENT_CALLBACKS(double) +HEYOKA_S11N_IMPLEMENT_EVENT_CALLBACKS(long double) + +HEYOKA_S11N_IMPLEMENT_BATCH_EVENT_CALLBACKS(float) +HEYOKA_S11N_IMPLEMENT_BATCH_EVENT_CALLBACKS(double) +HEYOKA_S11N_IMPLEMENT_BATCH_EVENT_CALLBACKS(long double) + +#if defined(HEYOKA_HAVE_REAL128) + +HEYOKA_S11N_IMPLEMENT_EVENT_CALLBACKS(mppp::real128) +HEYOKA_S11N_IMPLEMENT_BATCH_EVENT_CALLBACKS(mppp::real128) + +#endif + +#if defined(HEYOKA_HAVE_REAL) + +HEYOKA_S11N_IMPLEMENT_EVENT_CALLBACKS(mppp::real) + +#endif + +#undef HEYOKA_S11N_IMPLEMENT_EVENT_CALLBACKS +#undef HEYOKA_S11N_IMPLEMENT_BATCH_EVENT_CALLBACKS + +// Scalar and batch step callbacks. +HEYOKA_S11N_STEP_CALLBACK_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, float) +HEYOKA_S11N_STEP_CALLBACK_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, double) +HEYOKA_S11N_STEP_CALLBACK_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, long double) + +HEYOKA_S11N_STEP_CALLBACK_BATCH_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, float) +HEYOKA_S11N_STEP_CALLBACK_BATCH_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, double) +HEYOKA_S11N_STEP_CALLBACK_BATCH_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, long double) + +#if defined(HEYOKA_HAVE_REAL128) + +HEYOKA_S11N_STEP_CALLBACK_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, mppp::real128) +HEYOKA_S11N_STEP_CALLBACK_BATCH_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, mppp::real128) + +#endif + +#if defined(HEYOKA_HAVE_REAL) + +HEYOKA_S11N_STEP_CALLBACK_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, mppp::real) + +#endif diff --git a/test/callable.cpp b/test/callable.cpp index 3cc9d684d..310e76ad0 100644 --- a/test/callable.cpp +++ b/test/callable.cpp @@ -182,7 +182,7 @@ TEST_CASE("callable type idx") { callable c; - REQUIRE(c.get_type_index() == typeid(void (*)())); + REQUIRE(c.get_type_index() == typeid(detail::empty_callable)); auto f = []() {}; @@ -207,6 +207,7 @@ struct foo_s11n { }; HEYOKA_S11N_CALLABLE_EXPORT(foo_s11n, int, int) +HEYOKA_S11N_CALLABLE_EXPORT(heyoka::detail::empty_callable, int, int) TEST_CASE("callable s11n") { @@ -235,6 +236,30 @@ TEST_CASE("callable s11n") REQUIRE(!!c); REQUIRE(c(1) == 101); } + + // Test an empty callable too. + { + callable c; + + std::stringstream ss; + + { + boost::archive::binary_oarchive oa(ss); + + oa << c; + } + + c = callable(foo_s11n{100}); + REQUIRE(c); + + { + boost::archive::binary_iarchive ia(ss); + + ia >> c; + } + + REQUIRE(!c); + } } struct vfunc { @@ -245,8 +270,9 @@ struct vfunc { TEST_CASE("callable extract") { callable c; - REQUIRE(c.extract() != nullptr); - REQUIRE(std::as_const(c).extract() != nullptr); + REQUIRE(c.extract() == nullptr); + REQUIRE(c.extract() != nullptr); + REQUIRE(std::as_const(c).extract() == nullptr); c = callable(blap); REQUIRE(c.extract() != nullptr); diff --git a/test/step_callback.cpp b/test/step_callback.cpp index 3d9ba6af8..fc276ed05 100644 --- a/test/step_callback.cpp +++ b/test/step_callback.cpp @@ -87,7 +87,7 @@ TEST_CASE("step_callback basics") REQUIRE(!std::is_constructible_v, void>); REQUIRE(!std::is_constructible_v, int, int>); - REQUIRE(step_cb.get_type_index() == typeid(bool (*)(taylor_adaptive &))); + REQUIRE(step_cb.get_type_index() == typeid(detail::empty_callable)); // Copy construction of empty callback. auto step_cb2 = step_cb; @@ -286,6 +286,30 @@ TEST_CASE("step_callback s11n") REQUIRE(step_cb.template extract() != nullptr); } + // Empty step callback test. + { + step_callback step_cb; + + std::stringstream ss; + + { + boost::archive::binary_oarchive oa(ss); + + oa << step_cb; + } + + step_cb = step_callback{cb2{}}; + REQUIRE(step_cb); + + { + boost::archive::binary_iarchive ia(ss); + + ia >> step_cb; + } + + REQUIRE(!step_cb); + } + { step_callback_batch step_cb(cb2{}); @@ -309,6 +333,30 @@ TEST_CASE("step_callback s11n") REQUIRE(!!step_cb); REQUIRE(step_cb.template extract() != nullptr); } + + // Empty step callback test. + { + step_callback_batch step_cb; + + std::stringstream ss; + + { + boost::archive::binary_oarchive oa(ss); + + oa << step_cb; + } + + step_cb = step_callback_batch{cb2{}}; + REQUIRE(step_cb); + + { + boost::archive::binary_iarchive ia(ss); + + ia >> step_cb; + } + + REQUIRE(!step_cb); + } }; tuple_for_each(fp_types, tester); From d404fab36c152c64b381877e3250855dbb90dc1a Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 8 Dec 2023 13:37:57 +0100 Subject: [PATCH 2/5] lint fixes. --- src/detail/empty_callable_s11n.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/detail/empty_callable_s11n.cpp b/src/detail/empty_callable_s11n.cpp index f2c01faa0..5e64a7979 100644 --- a/src/detail/empty_callable_s11n.cpp +++ b/src/detail/empty_callable_s11n.cpp @@ -28,6 +28,8 @@ // NOTE: this file contains the code for registering default-constructed // event and step callbacks in the serialisation system. +// NOLINTBEGIN(cert-err58-cpp) + // Scalar and batch event callbacks. #define HEYOKA_S11N_IMPLEMENT_EVENT_CALLBACKS(T) \ HEYOKA_S11N_CALLABLE_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, void, heyoka::taylor_adaptive &, T, int) \ @@ -84,3 +86,5 @@ HEYOKA_S11N_STEP_CALLBACK_BATCH_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, HEYOKA_S11N_STEP_CALLBACK_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, mppp::real) #endif + +// NOLINTEND(cert-err58-cpp) From f168eaa377b1f1edfe16851289082ce1b097abf7 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 8 Dec 2023 14:54:28 +0100 Subject: [PATCH 3/5] Small simplification. --- include/heyoka/callable.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/heyoka/callable.hpp b/include/heyoka/callable.hpp index 36a45d4a2..0be0b2164 100644 --- a/include/heyoka/callable.hpp +++ b/include/heyoka/callable.hpp @@ -100,9 +100,8 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface }; // Implementation of the callable interface for the empty callable. -template - requires std::same_as -struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface +template +struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface : virtual callable_iface { explicit operator bool() const noexcept final { From 8fe3e85230080754d427a972df8900deaf72da72 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 8 Dec 2023 15:33:52 +0100 Subject: [PATCH 4/5] Some more small tweaks. --- include/heyoka/callable.hpp | 40 ++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/include/heyoka/callable.hpp b/include/heyoka/callable.hpp index 0be0b2164..2a58d677b 100644 --- a/include/heyoka/callable.hpp +++ b/include/heyoka/callable.hpp @@ -86,9 +86,16 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface } R operator()(Args... args) final { + using unrefT = std::remove_reference_t>; + // Check if this is empty before invoking the call operator. - if (!this->operator bool()) { - throw std::bad_function_call{}; + // NOTE: no check needed here for std::function or callable: in case + // of an empty object, the std::bad_function_call exception will be + // thrown by the call operator of the object. + if constexpr (std::is_pointer_v || std::is_member_pointer_v) { + if (this->value() == nullptr) { + throw std::bad_function_call{}; + } } if constexpr (std::is_same_v) { @@ -103,6 +110,8 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface template struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface : virtual callable_iface { + // NOTE: the empty callable is always empty and always results + // in an exception being thrown if called. explicit operator bool() const noexcept final { return false; @@ -165,20 +174,23 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_ref_iface { using type = callable_ref_iface_impl; }; +// Configuration of the callable wrap. +template +inline constexpr auto callable_wrap_config + = tanuki::config::template type>{ + // Similarly to std::function, ensure that callable can store + // in static storage pointers and reference wrappers. + // NOTE: reference wrappers are not guaranteed to have the size + // of a pointer, but in practice that should always be the case. + // In case this is a concern, static asserts can be added + // in the callable interface implementation. + .static_size = tanuki::holder_size, + .pointer_interface = false, + .explicit_generic_ctor = false}; + // Definition of the callable wrap. template -using callable_wrap_t = tanuki::wrap::template type>{ - // Similarly to std::function, ensure that callable can store - // in static storage pointers and reference wrappers. - // NOTE: reference wrappers are not guaranteed to have the size - // of a pointer, but in practice that should always be the case. - // In case this is a concern, static asserts can be added - // in the callable interface implementation. - .static_size = tanuki::holder_size, - .pointer_interface = false, - .explicit_generic_ctor = false}, - R, Args...>; +using callable_wrap_t = tanuki::wrap, R, Args...>; template struct callable_impl { From b9108361d786aec863305d01290d8e9b1dd9751d Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 8 Dec 2023 16:28:15 +0100 Subject: [PATCH 5/5] Fix missing include, add test. --- src/detail/empty_callable_s11n.cpp | 2 +- test/taylor_t_event.cpp | 58 ++++++++++++++++++++++-------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/detail/empty_callable_s11n.cpp b/src/detail/empty_callable_s11n.cpp index 5e64a7979..40590b87a 100644 --- a/src/detail/empty_callable_s11n.cpp +++ b/src/detail/empty_callable_s11n.cpp @@ -10,8 +10,8 @@ #include #include -#include #include +#include #if defined(HEYOKA_HAVE_REAL128) diff --git a/test/taylor_t_event.cpp b/test/taylor_t_event.cpp index 4b7046365..81429a57a 100644 --- a/test/taylor_t_event.cpp +++ b/test/taylor_t_event.cpp @@ -1113,29 +1113,57 @@ TEST_CASE("t s11n") auto [x, v] = make_vars("x", "v"); - t_event ev(v, kw::callback = s11n_callback{}, kw::direction = event_direction::positive, - kw::cooldown = fp_t(100)); + { + t_event ev(v, kw::callback = s11n_callback{}, kw::direction = event_direction::positive, + kw::cooldown = fp_t(100)); - std::stringstream ss; + std::stringstream ss; - { - boost::archive::binary_oarchive oa(ss); + { + boost::archive::binary_oarchive oa(ss); - oa << ev; - } + oa << ev; + } - ev = t_event(v + x); + ev = t_event(v + x); - { - boost::archive::binary_iarchive ia(ss); + { + boost::archive::binary_iarchive ia(ss); - ia >> ev; + ia >> ev; + } + + REQUIRE(ev.get_expression() == v); + REQUIRE(ev.get_direction() == event_direction::positive); + REQUIRE(ev.get_callback().get_type_index() == typeid(s11n_callback)); + REQUIRE(ev.get_cooldown() == fp_t(100)); } - REQUIRE(ev.get_expression() == v); - REQUIRE(ev.get_direction() == event_direction::positive); - REQUIRE(ev.get_callback().get_type_index() == typeid(s11n_callback)); - REQUIRE(ev.get_cooldown() == fp_t(100)); + // Try also a terminal event with empty callback. + { + t_event ev(v, kw::direction = event_direction::positive, kw::cooldown = fp_t(100)); + + std::stringstream ss; + + { + boost::archive::binary_oarchive oa(ss); + + oa << ev; + } + + ev = t_event(v + x); + + { + boost::archive::binary_iarchive ia(ss); + + ia >> ev; + } + + REQUIRE(ev.get_expression() == v); + REQUIRE(ev.get_direction() == event_direction::positive); + REQUIRE(!ev.get_callback()); + REQUIRE(ev.get_cooldown() == fp_t(100)); + } }; tuple_for_each(fp_types, tester);