@@ -43,107 +43,124 @@ inline constexpr bool is_any_std_func<std::function<R(Args...)>> = true;
4343template <typename >
4444inline constexpr bool is_any_callable = false ;
4545
46- // An empty struct used in the default initialisation
47- // of callable objects.
48- // NOTE: we use this rather than, e.g., a null function
49- // pointer so that we can enable serialisation of
50- // default-constructed callables.
51- struct HEYOKA_DLL_PUBLIC_INLINE_CLASS empty_callable {
52- template <typename Archive>
53- void serialize (Archive &, unsigned )
54- {
55- }
56- };
46+ // Base interface for callable objects.
47+ //
48+ // The base interface contains the bool conversion operator.
49+ //
50+ // NOLINTNEXTLINE(cppcoreguidelines-special-member-functions,hicpp-special-member-functions,cppcoreguidelines-virtual-class-destructor)
51+ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS base_callable_iface {
52+ virtual explicit operator bool () const noexcept = 0;
5753
58- // Default (empty) implementation of the callable interface.
59- template <typename , typename , typename , typename , typename ...>
60- struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface_impl {
54+ // Default implementation.
55+ template <typename Base, typename T>
56+ struct impl : public Base {
57+ explicit operator bool () const noexcept final
58+ {
59+ // NOTE: make sure to fully unwrap T (including const qualifiers), otherwise we will misdetect
60+ // callable/std::function below if we are wrapping a const reference.
61+ using unrefT = tanuki::unwrap_cvref_t <T>;
62+
63+ if constexpr (std::is_pointer_v<unrefT> || std::is_member_pointer_v<unrefT>) {
64+ return getval (this ) != nullptr ;
65+ } else if constexpr (is_any_callable<unrefT> || is_any_std_func<unrefT>) {
66+ return static_cast <bool >(getval (this ));
67+ } else {
68+ return true ;
69+ }
70+ }
71+ };
6172};
6273
63- // Implementation of the callable interface for invocable objects.
64- template <typename Base, typename Holder, typename T, typename R, typename ... Args>
65- requires std::is_invocable_r_v<R, std::remove_reference_t <std::unwrap_reference_t <T>> &, Args...>
66- // NOTE: also require copy constructibility like
67- // std::function does.
68- && std::copy_constructible<T>
69- struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface_impl<Base, Holder, T, R, Args...> : public Base {
70- explicit operator bool () const noexcept final
71- {
72- using unrefT = std::remove_reference_t <std::unwrap_reference_t <T>>;
74+ // The two interfaces for const and mutable callable objects.
75+ template <typename R, typename ... Args>
76+ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS const_callable_iface : base_callable_iface {
77+ virtual R operator ()(Args... args) const = 0;
78+ };
7379
74- if constexpr (std::is_pointer_v<unrefT> || std::is_member_pointer_v<unrefT>) {
75- return getval<Holder>(this ) != nullptr ;
76- } else if constexpr (is_any_callable<unrefT> || is_any_std_func<unrefT>) {
77- return static_cast <bool >(getval<Holder>(this ));
78- } else {
79- return true ;
80- }
81- }
82- R operator ()(Args... args) final
83- {
84- using unrefT = std::remove_reference_t <std::unwrap_reference_t <T>>;
85-
86- // Check if this is empty before invoking the call operator.
87- // NOTE: no check needed here for std::function or callable: in case
88- // of an empty object, the std::bad_function_call exception will be
89- // thrown by the call operator of the object.
90- if constexpr (std::is_pointer_v<unrefT> || std::is_member_pointer_v<unrefT>) {
91- if (getval<Holder>(this ) == nullptr ) {
92- throw std::bad_function_call{};
93- }
94- }
80+ template <typename R, typename ... Args>
81+ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS mutable_callable_iface : base_callable_iface {
82+ virtual R operator ()(Args... args) = 0;
83+ };
9584
96- if constexpr (std::is_same_v<R, void >) {
97- static_cast <void >(std::invoke (getval<Holder>(this ), std::forward<Args>(args)...));
98- } else {
99- return std::invoke (getval<Holder>(this ), std::forward<Args>(args)...);
85+ // Implementation of the call operator for the callable interface. We need both a const and a mutable variant with
86+ // identical code, so we move the implementation outside.
87+ template <typename T, typename R, typename Impl, typename ... Args>
88+ R callable_call_operator (Impl *self, Args &&...args)
89+ {
90+ using unrefT = tanuki::unwrap_cvref_t <T>;
91+
92+ // Check if this is empty before invoking the call operator.
93+ //
94+ // NOTE: no check needed here for std::function or callable: in case of an empty object, the
95+ // std::bad_function_call exception will be thrown by the call operator of the object.
96+ if constexpr (std::is_pointer_v<unrefT> || std::is_member_pointer_v<unrefT>) {
97+ if (getval (self) == nullptr ) [[unlikely]] {
98+ throw std::bad_function_call{};
10099 }
101100 }
102- };
103101
104- // Implementation of the callable interface for the empty callable.
105- template <typename Base, typename Holder, typename R, typename ... Args>
106- struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface_impl<Base, Holder, empty_callable, R, Args...> : public Base {
107- // NOTE: the empty callable is always empty and always results
108- // in an exception being thrown if called.
109- explicit operator bool () const noexcept final
110- {
111- return false ;
102+ if constexpr (std::is_same_v<R, void >) {
103+ static_cast <void >(std::invoke (getval (self), std::forward<Args>(args)...));
104+ } else {
105+ return std::invoke (getval (self), std::forward<Args>(args)...);
112106 }
113- [[noreturn]] R operator ()(Args...) final
114- {
115- throw std::bad_function_call{};
116- }
117- };
107+ }
118108
119109// Definition of the callable interface.
120- template <typename R, typename ... Args>
110+ //
111+ // This inherits from either const_callable_iface or mutable_callable_iface, depending on the Const flag.
112+ template <bool Const, typename R, typename ... Args>
121113// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)
122- struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface {
123- virtual R operator ()(Args... args) = 0;
124- virtual explicit operator bool () const noexcept = 0;
125-
126- template <typename Base, typename Holder, typename T>
127- using impl = callable_iface_impl<Base, Holder, T, R, Args...>;
114+ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_iface
115+ : std::conditional_t <Const, const_callable_iface<R, Args...>, mutable_callable_iface<R, Args...>> {
116+ // Default (empty) implementation.
117+ template <typename , typename >
118+ struct impl {
119+ };
120+
121+ // Implementation for mutable invocable objects.
122+ template <typename Base, typename T>
123+ requires (!Const)
124+ && std::is_invocable_r_v<R, tanuki::unwrap_cvref_t <T> &, Args...>
125+ // NOTE: here we are also checking that T is not a const reference wrapper, which would lead to a
126+ // runtime exception when invoking getval() in the call operator. Like this, we move the error to
127+ // compile time.
128+ && (!is_reference_wrapper<T> || !std::is_const_v<std::remove_reference_t <std::unwrap_reference_t <T>>>)
129+ // NOTE: also require copy constructibility like std::function does.
130+ && std::copy_constructible<T>
131+ struct impl <Base, T> : base_callable_iface::impl<Base, T> {
132+ R operator ()(Args... args) final
133+ {
134+ return callable_call_operator<T, R>(this , std::forward<Args>(args)...);
135+ }
136+ };
137+
138+ // Implementation for const invocable objects.
139+ template <typename Base, typename T>
140+ requires Const
141+ && std::is_invocable_r_v<R, const tanuki::unwrap_cvref_t <T> &, Args...> && std::copy_constructible<T>
142+ struct impl <Base, T> : base_callable_iface::impl<Base, T> {
143+ R operator ()(Args... args) const final
144+ {
145+ return callable_call_operator<T, R>(this , std::forward<Args>(args)...);
146+ }
147+ };
128148};
129149
130150// Implementation of the reference interface.
131151template <typename R, typename ... Args>
132152struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_ref_iface {
133- template <typename Wrap, typename ... FArgs>
134- requires requires (Wrap &&self, FArgs &&...fargs) {
135- {
136- std::forward_like<Wrap> (*iface_ptr (std::forward<Wrap>(self)))(std::forward<FArgs>(fargs)...)
137- } -> std::same_as<R>;
138- }
139- R operator ()(this Wrap &&self, FArgs &&...fargs)
153+ // NOTE: thanks to the "deducing this" feature, we need just one implementation of the call operator, which works
154+ // for both the const and mutable variants.
155+ template <typename Wrap>
156+ R operator ()(this Wrap &&self, Args... args)
140157 {
141158 // NOTE: a wrap in invalid state is considered empty.
142159 if (is_invalid (self)) [[unlikely]] {
143160 throw std::bad_function_call{};
144161 }
145162
146- return std::forward_like<Wrap>(* iface_ptr (std::forward<Wrap>(self)))(std::forward<FArgs>(fargs )...);
163+ return iface_ptr (std::forward<Wrap>(self))-> operator ( )(std::forward<Args>(args )...);
147164 }
148165
149166 template <typename Wrap>
@@ -159,40 +176,45 @@ struct HEYOKA_DLL_PUBLIC_INLINE_CLASS callable_ref_iface {
159176};
160177
161178// Configuration of the callable wrap.
162- template <typename R, typename ... Args>
163- inline constexpr auto callable_wrap_config = tanuki::config<empty_callable, callable_ref_iface<R, Args...>>{
164- // Similarly to std::function, ensure that callable can store
165- // in static storage pointers and reference wrappers.
166- // NOTE: reference wrappers are not guaranteed to have the size
167- // of a pointer, but in practice that should always be the case.
168- // In case this is a concern, static asserts can be added
169- // in the callable interface implementation.
170- .static_size = tanuki::holder_size<R (*)(Args...), callable_iface<R, Args...>>,
179+ template <bool Const, typename R, typename ... Args>
180+ inline constexpr auto callable_wrap_config = tanuki::config<void , callable_ref_iface<R, Args...>>{
181+ // Similarly to std::function, ensure that callable can store in static storage pointers and reference wrappers.
182+ //
183+ // NOTE: reference wrappers are not guaranteed to have the size of a pointer, but in practice that should always be
184+ // the case. In case this is a concern, static asserts can be added in the callable interface implementation.
185+ .static_size = tanuki::holder_size<R (*)(Args...), callable_iface<Const, R, Args...>>,
186+ .invalid_default_ctor = true ,
171187 .pointer_interface = false ,
172188 .explicit_ctor = tanuki::wrap_ctor::always_implicit};
173189
174190// Definition of the callable wrap.
175- template <typename R, typename ... Args>
176- using callable_wrap_t = tanuki::wrap<callable_iface<R, Args...>, callable_wrap_config<R, Args...>>;
191+ template <bool Const, typename R, typename ... Args>
192+ using callable_wrap_t = tanuki::wrap<callable_iface<Const, R, Args...>, callable_wrap_config<Const, R, Args...>>;
177193
178194// Specialise is_any_callable to detect callables.
179- template <typename R, typename ... Args>
180- inline constexpr bool is_any_callable<detail::callable_wrap_t <R, Args...>> = true ;
195+ template <bool Const, typename R, typename ... Args>
196+ inline constexpr bool is_any_callable<detail::callable_wrap_t <Const, R, Args...>> = true ;
181197
198+ // Helper to select the const or mutable callable wrap variant.
182199template <typename T>
183- struct callable_impl {
200+ struct callable_impl_selector {
184201};
185202
186203template <typename R, typename ... Args>
187- struct callable_impl <R(Args...)> {
188- using type = callable_wrap_t <R, Args...>;
204+ struct callable_impl_selector <R(Args...)> {
205+ using type = callable_wrap_t <false , R, Args...>;
206+ };
207+
208+ template <typename R, typename ... Args>
209+ struct callable_impl_selector <R(Args...) const > {
210+ using type = callable_wrap_t <true , R, Args...>;
189211};
190212
191213} // namespace detail
192214
193215template <typename T>
194- requires (requires () { typename detail::callable_impl <T>::type; })
195- using callable = typename detail::callable_impl <T>::type;
216+ requires (requires () { typename detail::callable_impl_selector <T>::type; })
217+ using callable = typename detail::callable_impl_selector <T>::type;
196218
197219HEYOKA_END_NAMESPACE
198220
@@ -203,9 +225,10 @@ HEYOKA_END_NAMESPACE
203225#endif
204226
205227// Serialisation macros.
206- // NOTE: by default, we build a custom name and pass it to TANUKI_S11N_WRAP_EXPORT_KEY2.
207- // This allows us to reduce the size of the final guid wrt to what TANUKI_S11N_WRAP_EXPORT_KEY
208- // would synthesise, and thus to ameliorate the "class name too long" issue.
228+ //
229+ // NOTE: by default, we build a custom name and pass it to TANUKI_S11N_WRAP_EXPORT_KEY2. This allows us to reduce the
230+ // size of the final guid wrt to what TANUKI_S11N_WRAP_EXPORT_KEY would synthesise, and thus to ameliorate the "class
231+ // name too long" issue.
209232#define HEYOKA_S11N_CALLABLE_EXPORT_KEY (udc, ...) \
210233 TANUKI_S11N_WRAP_EXPORT_KEY2 (udc, " heyoka::callable<" #__VA_ARGS__ " >@" #udc, \
211234 heyoka::detail::callable_iface<__VA_ARGS__>)
0 commit comments