Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore s11n capabilities for empty event and step callbacks #371

Merged
merged 5 commits into from
Dec 8, 2023
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
83 changes: 58 additions & 25 deletions include/heyoka/callable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename Archive>
void serialize(Archive &, unsigned)
{
}
};

// Declaration of the callable interface template.
template <typename, typename, typename, typename...>
struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface {
Expand All @@ -60,37 +72,53 @@ template <typename Holder, typename T, typename R, typename... Args>
&& std::copy_constructible<T>
struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface<Holder, T, R, Args...>
: virtual callable_iface<void, void, R, Args...>, tanuki::iface_impl_helper<Holder, T, callable_iface, R, Args...> {
explicit operator bool() const noexcept final
{
using unrefT = std::remove_reference_t<std::unwrap_reference_t<T>>;

if constexpr (std::is_pointer_v<unrefT> || std::is_member_pointer_v<unrefT>) {
return this->value() != nullptr;
} else if constexpr (is_any_callable<unrefT>::value || is_any_std_func_v<unrefT>) {
return static_cast<bool>(this->value());
} else {
return true;
}
}
R operator()(Args... args) final
{
using unrefT = std::remove_reference_t<std::unwrap_reference_t<T>>;

// Check if this is empty before invoking the call operator.
// 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<unrefT> || std::is_member_pointer_v<unrefT>) {
if (this->value() == nullptr) {
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<R, void>) {
static_cast<void>(std::invoke(this->value(), std::forward<Args>(args)...));
} else {
return std::invoke(this->value(), std::forward<Args>(args)...);
}
}
};

// Implementation of the callable interface for the empty callable.
template <typename Holder, typename R, typename... Args>
struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface<Holder, empty_callable, R, Args...>
: virtual callable_iface<void, void, R, Args...> {
// NOTE: the empty callable is always empty and always results
// in an exception being thrown if called.
explicit operator bool() const noexcept final
{
using unrefT = std::remove_reference_t<std::unwrap_reference_t<T>>;

if constexpr (std::is_pointer_v<unrefT> || std::is_member_pointer_v<unrefT>) {
return this->value() != nullptr;
} else if constexpr (is_any_callable<unrefT>::value || is_any_std_func_v<unrefT>) {
return static_cast<bool>(this->value());
} else {
return true;
}
return false;
}
[[noreturn]] R operator()(Args...) final
{
throw std::bad_function_call{};
}
};

Expand All @@ -103,6 +131,7 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_ref_iface_impl {
auto operator()(FArgs &&...fargs)
-> decltype(iface_ptr(*static_cast<JustWrap *>(this))->operator()(std::forward<FArgs>(fargs)...))
{
// NOTE: a wrap in invalid state is considered empty.
if (is_invalid(*static_cast<Wrap *>(this))) {
throw std::bad_function_call{};
}
Expand All @@ -112,6 +141,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<const Wrap *>(this))) {
return false;
} else {
Expand Down Expand Up @@ -144,20 +174,23 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_ref_iface {
using type = callable_ref_iface_impl<Wrap, R, Args...>;
};

// Configuration of the callable wrap.
template <typename R, typename... Args>
inline constexpr auto callable_wrap_config
= tanuki::config<empty_callable, callable_ref_iface<R, Args...>::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<R (*)(Args...), callable_iface, R, Args...>,
.pointer_interface = false,
.explicit_generic_ctor = false};

// Definition of the callable wrap.
template <typename R, typename... Args>
using callable_wrap_t = tanuki::wrap<callable_iface,
tanuki::config<R (*)(Args...), callable_ref_iface<R, Args...>::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<R (*)(Args...), callable_iface, R, Args...>,
.pointer_interface = false,
.explicit_generic_ctor = false},
R, Args...>;
using callable_wrap_t = tanuki::wrap<callable_iface, callable_wrap_config<R, Args...>, R, Args...>;

template <typename T>
struct callable_impl {
Expand Down
24 changes: 23 additions & 1 deletion include/heyoka/step_callback.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS step_cb_ifaceT {

// Configuration.
template <typename TA>
inline constexpr auto step_cb_wrap_config = tanuki::config<bool (*)(TA &), step_cb_ref_iface<TA>::template type>{
inline constexpr auto step_cb_wrap_config = tanuki::config<empty_callable, step_cb_ref_iface<TA>::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
Expand Down Expand Up @@ -264,4 +264,26 @@ HEYOKA_S11N_STEP_CALLBACK_EXPORT_KEY(heyoka::step_callback_set<mppp::real>, 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
35 changes: 35 additions & 0 deletions include/heyoka/taylor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2006,4 +2006,39 @@ BOOST_CLASS_VERSION(heyoka::taylor_adaptive_batch<mppp::real>, 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> &, T, int) \
HEYOKA_S11N_CALLABLE_EXPORT_KEY(heyoka::detail::empty_callable, bool, heyoka::taylor_adaptive<T> &, 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> &, T, int, \
std::uint32_t) \
HEYOKA_S11N_CALLABLE_EXPORT_KEY(heyoka::detail::empty_callable, bool, heyoka::taylor_adaptive_batch<T> &, 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
90 changes: 90 additions & 0 deletions src/detail/empty_callable_s11n.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2020, 2021, 2022, 2023 Francesco Biscani ([email protected]), Dario Izzo ([email protected])
//
// 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 <cstdint>

#include <heyoka/callable.hpp>
#include <heyoka/config.hpp>
#include <heyoka/step_callback.hpp>
#include <heyoka/taylor.hpp>

#if defined(HEYOKA_HAVE_REAL128)

#include <mp++/real128.hpp>

#endif

#if defined(HEYOKA_HAVE_REAL)

#include <mp++/real.hpp>

#endif

// 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> &, T, int) \
HEYOKA_S11N_CALLABLE_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, bool, heyoka::taylor_adaptive<T> &, 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> &, T, \
int, std::uint32_t) \
HEYOKA_S11N_CALLABLE_EXPORT_IMPLEMENT(heyoka::detail::empty_callable, bool, heyoka::taylor_adaptive_batch<T> &, \
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

// NOLINTEND(cert-err58-cpp)
32 changes: 29 additions & 3 deletions test/callable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ TEST_CASE("callable type idx")
{
callable<void()> c;

REQUIRE(c.get_type_index() == typeid(void (*)()));
REQUIRE(c.get_type_index() == typeid(detail::empty_callable));

auto f = []() {};

Expand All @@ -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")
{
Expand Down Expand Up @@ -235,6 +236,30 @@ TEST_CASE("callable s11n")
REQUIRE(!!c);
REQUIRE(c(1) == 101);
}

// Test an empty callable too.
{
callable<int(int)> c;

std::stringstream ss;

{
boost::archive::binary_oarchive oa(ss);

oa << c;
}

c = callable<int(int)>(foo_s11n{100});
REQUIRE(c);

{
boost::archive::binary_iarchive ia(ss);

ia >> c;
}

REQUIRE(!c);
}
}

struct vfunc {
Expand All @@ -245,8 +270,9 @@ struct vfunc {
TEST_CASE("callable extract")
{
callable<void()> c;
REQUIRE(c.extract<void (*)()>() != nullptr);
REQUIRE(std::as_const(c).extract<void (*)()>() != nullptr);
REQUIRE(c.extract<void (*)()>() == nullptr);
REQUIRE(c.extract<detail::empty_callable>() != nullptr);
REQUIRE(std::as_const(c).extract<void (*)()>() == nullptr);

c = callable<void()>(blap);
REQUIRE(c.extract<void (*)()>() != nullptr);
Expand Down
Loading