Skip to content

Commit

Permalink
Merge pull request #371 from bluescarni/pr/callable_fixes
Browse files Browse the repository at this point in the history
Restore s11n capabilities for empty event and step callbacks
  • Loading branch information
bluescarni authored Dec 8, 2023
2 parents c82f146 + b910836 commit 1a52c5c
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 45 deletions.
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

0 comments on commit 1a52c5c

Please sign in to comment.