Photon Engine 2.0.0-beta
A physically based renderer.
Loading...
Searching...
No Matches
TFunction.h
Go to the documentation of this file.
1#pragma once
2
3#include <Common/assertion.h>
4#include <Common/config.h>
5#include <Common/exceptions.h>
6#include <Common/memory.h>
7
8#include <cstddef>
9#include <type_traits>
10#include <utility>
11#include <new>
12#include <memory>
13#include <algorithm>
14
15namespace ph
16{
17
18namespace function_detail
19{
20
21template<typename T, std::size_t MIN_SIZE_HINT = 0>
22class TFunction final
23{
24 // Correct function signature will instantiate the specialized type. If this type is selected
25 // instead, notify the user about the ill-formed function signature
26 static_assert(std::is_function_v<T>,
27 "Invalid function signature.");
28};
29
30template<auto Func, typename R, typename... Args>
32 std::is_function_v<std::remove_pointer_t<decltype(Func)>> &&
33 std::is_invocable_r_v<R, decltype(Func), Args...>;
34
35template<auto Func, typename Class, typename R, typename... Args>
37 std::is_member_function_pointer_v<decltype(Func)> &&
38 std::is_invocable_r_v<R, decltype(Func), const Class*, Args...>;
39
40template<auto Func, typename Class, typename R, typename... Args>
42 std::is_member_function_pointer_v<decltype(Func)> &&
43 std::is_invocable_r_v<R, decltype(Func), Class*, Args...>;
44
45template<typename Func, typename R, typename... Args>
47 std::is_empty_v<std::decay_t<Func>> &&// so we do not need to store its states
48 std::is_default_constructible_v<std::decay_t<Func>> &&// we construct it on every call
49 std::is_invocable_r_v<R, std::decay_t<Func>, Args...>;// we call from the newly constructed value, no matter the constness
50
51template<typename Func, typename R, typename... Args>
53 !std::is_empty_v<std::decay_t<Func>> &&// to disambiguate from the empty form
54 !std::is_function_v<std::remove_pointer_t<std::decay_t<Func>>> &&// to disambiguate from the free function form
55 (
56 std::is_constructible_v<std::decay_t<Func>, Func&&> ||// we can placement new from an instance
57 std::is_trivially_copyable_v<std::decay_t<Func>> // or copy to a byte buffer
58 ) &&
59 std::is_trivially_destructible_v<std::decay_t<Func>> &&// we are neither storing dtor nor calling it
60 std::is_invocable_r_v<R, const std::decay_t<Func>&, Args...>;// must be const as we store its states and `operator ()` is `const`
61
80template<typename R, typename... Args, std::size_t MIN_SIZE_HINT>
81class TFunction<R(Args...), MIN_SIZE_HINT> final
82{
83private:
84 using UnifiedCaller = R(*)(const TFunction*, Args...);
85
86 // Aligning to the pointer size should be sufficient in most cases. Currently we do not align the
87 // buffer to `std::max_align_t` or anything greater to save space.
88 constexpr static std::size_t BUFFER_ALIGNMENT = alignof(void*);
89
90 constexpr static std::size_t BUFFER_SIZE = MIN_SIZE_HINT > sizeof(UnifiedCaller) + sizeof(void*)
91 ? MIN_SIZE_HINT - sizeof(UnifiedCaller)
92 : sizeof(void*);
93
94public:
97 template<typename Func>
98 using TCanFitBuffer = std::bool_constant<
99 sizeof(std::decay_t<Func>) <= BUFFER_SIZE &&
100 alignof(std::decay_t<Func>) <= BUFFER_ALIGNMENT>;
101
106
109 template<auto Func>
110 using TIsFreeFunction = std::bool_constant<CFreeFunctionForm<Func, R, Args...>>;
111
116 template<auto Func, typename Class>
117 using TIsConstCallableMethod = std::bool_constant<CConstCallableMethodForm<Func, Class, R, Args...>>;
118
124 template<auto Func, typename Class>
125 using TIsNonConstCallableMethod = std::bool_constant<CNonConstCallableMethodForm<Func, Class, R, Args...>>;
126
132 template<typename Func>
133 using TIsEmptyFunctor = std::bool_constant<CEmptyFunctorForm<Func, R, Args...>>;
134
139 template<typename Func>
140 using TIsNonEmptyFunctor = std::bool_constant<
141 CNonEmptyFunctorForm<Func, R, Args...> &&
142 (
143 TCanFitBuffer<Func>::value || // for placement new into the buffer
144 sizeof(std::decay_t<Func>) <= BUFFER_SIZE// for bytewise copy
145 )>;
146
148
151 template<typename Func>
152 using TIsStorableFunctor = std::bool_constant<
155
156public:
159 inline TFunction() = default;
160
163 inline TFunction(std::nullptr_t /* ptr */)
164 : TFunction()
165 {}
166
169 template<typename Func>
170 inline TFunction(Func&& func)
171 requires (!std::is_same_v<std::decay_t<Func>, TFunction>)// avoid ambiguity during copy init
172 : TFunction()
173 {
174 if constexpr(TIsEmptyFunctor<Func>::value)
175 {
176 set<Func>();
177 }
178 else
179 {
181 "Cannot direct-init TFunction with the input functor. Possible cause of errors: "
182 "(1) sizeof functor exceeds current limit, reduce functor size or increase the limit; "
183 "(2) Invalid/mismatched functor signature; "
184 "(3) The direct-init ctor only works for functors. For other function types, please use setters.");
185
186 set<Func>(std::forward<Func>(func));
187 }
188 }
189
190 inline TFunction(const TFunction& other) = default;
191 inline TFunction(TFunction&& other) noexcept = default;
192 inline TFunction& operator = (const TFunction& rhs) = default;
193 inline TFunction& operator = (TFunction&& rhs) noexcept = default;
194 inline ~TFunction() = default;
195
199 template<typename... DeducedArgs>
200 inline R operator () (DeducedArgs&&... args) const
201 requires std::is_invocable_v<R(Args...), DeducedArgs...>
202 {
203 return (*m_caller)(this, std::forward<DeducedArgs>(args)...);
204 }
205
208 template<auto Func>
209 inline TFunction& set()
210 requires TIsFreeFunction<Func>::value
211 {
212 m_data.u_emptyStruct = EmptyStruct{};
213 m_caller = &freeFunctionCaller<Func>;
214
215 return *this;
216 }
217
220 template<auto Func, typename Class>
221 inline TFunction& set(const Class* const instancePtr)
223 {
224 PH_ASSERT(instancePtr);
225
226 m_data.u_constInstance = instancePtr;
227 m_caller = &constCallableMethodCaller<Func, Class>;
228
229 return *this;
230 }
231
234 template<auto Func, typename Class>
235 inline TFunction& set(Class* const instancePtr)
237 {
238 PH_ASSERT(instancePtr);
239
240 m_data.u_nonConstInstance = instancePtr;
241 m_caller = &nonConstCallableMethodCaller<Func, Class>;
242
243 return *this;
244 }
245
248 template<typename Func>
249 inline TFunction& set()
250 requires TIsEmptyFunctor<Func>::value
251 {
252 m_data.u_emptyStruct = EmptyStruct{};
253 m_caller = &emptyFunctorCaller<std::decay_t<Func>>;
254
255 return *this;
256 }
257
260 template<typename Func>
261 inline TFunction& set(const Func& /* unused */)
263 {
264 return set<Func>();
265 }
266
269 template<typename Func>
270 inline TFunction& set(Func&& func)
272 {
273 using Functor = std::decay_t<Func>;
274
275 // Favor constructed functor since it is more efficient in general
276 if constexpr(std::is_constructible_v<Functor, Func&&>)
277 {
278 // IOC of array of size 1
279 Functor* const storage = start_implicit_lifetime_as<Functor>(m_data.u_buffer);
280
281 std::construct_at(storage, std::forward<Func>(func));
282 m_caller = &nonEmptyConstructedFunctorCaller<Functor>;
283 }
284 else
285 {
286 static_assert(std::is_trivially_copyable_v<Functor>);
287
288 std::copy_n(reinterpret_cast<const std::byte*>(&func), sizeof(Functor), m_data.u_buffer);
289 m_caller = &nonEmptyCopiedFunctorCaller<Functor>();
290 }
291
292 return *this;
293 }
294
297 inline bool isValid() const
298 {
299 return m_caller != &invalidFunctionCaller;
300 }
301
304 inline operator bool () const
305 {
306 return isValid();
307 }
308
312 inline void unset()
313 {
314 m_caller = &invalidFunctionCaller;
315 }
316
317private:
318 template<auto Func>
319 inline static R freeFunctionCaller(const TFunction* /* unused */, Args... args)
320 requires TIsFreeFunction<Func>::value
321 {
322 return (*Func)(std::forward<Args>(args)...);
323 }
324
325 template<auto Func, typename Class>
326 inline static R constCallableMethodCaller(const TFunction* const self, Args... args)
327 requires TIsConstCallableMethod<Func, Class>::value
328 {
329 const auto* const instancePtr = static_cast<const Class*>(self->m_data.u_constInstance);
330 return (instancePtr->*Func)(std::forward<Args>(args)...);
331 }
332
333 template<auto Func, typename Class>
334 inline static R nonConstCallableMethodCaller(const TFunction* const self, Args... args)
335 requires TIsNonConstCallableMethod<Func, Class>::value
336 {
337 auto* const instancePtr = static_cast<Class*>(self->m_data.u_nonConstInstance);
338
339 return (instancePtr->*Func)(std::forward<Args>(args)...);
340 }
341
342 template<typename Func>
343 inline static R emptyFunctorCaller(const TFunction* /* unused */, Args... args)
344 requires TIsEmptyFunctor<Func>::value
345 {
346 // Under the assumption that a stateless functor should be cheap to create (and without any
347 // side effects), we construct a new `Func` on every call to it
348 return Func{}(std::forward<Args>(args)...);
349 }
350
351 template<typename Func>
352 inline static R nonEmptyConstructedFunctorCaller(const TFunction* const self, Args... args)
353 {
354 // We do not obtain the pointer to `Func` via placement new (or `std::construct_at`).
355 // Instead, we cast it from raw buffer and laundering is required by the standard
356 const auto& func = *std::launder(reinterpret_cast<const Func*>(self->m_data.u_buffer));
357
358 return func(std::forward<Args>(args)...);
359 }
360
361 template<typename Func>
362 inline static R nonEmptyCopiedFunctorCaller(const TFunction* const self, Args... args)
363 {
364 static_assert(std::is_trivially_copyable_v<Func> && std::is_default_constructible_v<Func>,
365 "Current implementation of copied functor caller requires default constructible functor "
366 "to copy bytes into.");
367
368 Func func;
369 std::copy_n(self->m_data.u_buffer, sizeof(Func), reinterpret_cast<std::byte*>(&func));
370
371 return func(std::forward<Args>(args)...);
372 }
373
374 [[noreturn]]
375 inline static R invalidFunctionCaller(const TFunction* /* unused */, Args... args)
376 {
377 throw UninitializedObjectException("Invalid function call: function is not set");
378 }
379
380private:
381 struct EmptyStruct
382 {};
383
384 union Data
385 {
386 // Intentionally provided so that default init of the union is a no-op.
387 EmptyStruct u_emptyStruct;
388
389 // Pointer to const class instance. May be empty except for methods.
390 const void* u_constInstance;
391
392 // Pointer to non-const class instance. May be empty except for methods.
393 void* u_nonConstInstance;
394
395 // Buffer for non-empty functors.
396 alignas(BUFFER_ALIGNMENT) std::byte u_buffer[BUFFER_SIZE];
397 };
398
399 // Ensure we are not wasting memory. Adjust buffer alignment if failed.
400 static_assert(alignof(Data) == BUFFER_ALIGNMENT);
401
402 // Member variables: smallest possible size of `TFunction` is two pointers--one for `UnifiedCaller`
403 // and another one in `Data`
404
405 Data m_data = {EmptyStruct{}};
406
407 // Wrapper function with unified signature for calling the actual function.
408 UnifiedCaller m_caller = &invalidFunctionCaller;
409};
410
411}// end namespace function_detail
412
413template<typename Func, std::size_t MIN_SIZE_HINT = PH_TFUNCTION_DEFAULT_MIN_SIZE_IN_BYTES>
415
416// This is a stricter requirement than what `TFunction` guaranteed. However, if its code works
417// correctly the size should be exactly what has been requested (providing the hint is >= 16 bytes).
418static_assert(sizeof(TFunction<int(int, int)>) == PH_TFUNCTION_DEFAULT_MIN_SIZE_IN_BYTES);
419
420}// end namespace ph
TFunction & set(const Class *const instancePtr)
Set a method callable using a const instance.
Definition TFunction.h:221
std::bool_constant< CNonEmptyFunctorForm< Func, R, Args... > &&( TCanFitBuffer< Func >::value|| sizeof(std::decay_t< Func >)<=BUFFER_SIZE)> TIsNonEmptyFunctor
Check if the type Func is a functor with member variable and can be stored internally....
Definition TFunction.h:140
TFunction()=default
Creates an invalid function that cannot be called.
TFunction(Func &&func)
Creates a function from functors (including lambdas).
Definition TFunction.h:170
TFunction & set()
Set a free function.
Definition TFunction.h:209
TFunction & set(Class *const instancePtr)
Set a method callable using a non-const instance.
Definition TFunction.h:235
void unset()
Clear the stored function. The function becomes invalid after this call.
Definition TFunction.h:312
bool isValid() const
Check if this function can be called.
Definition TFunction.h:297
TFunction & set(const Func &)
Set an empty functor or lambda without capture from object.
Definition TFunction.h:261
std::bool_constant< CNonConstCallableMethodForm< Func, Class, R, Args... > > TIsNonConstCallableMethod
Test if the target Func is a method and is invocable with a non-const object. Note that the test is f...
Definition TFunction.h:125
std::bool_constant< TIsEmptyFunctor< Func >::value|| TIsNonEmptyFunctor< Func >::value > TIsStorableFunctor
Convenient helper that checks whether Func is a supported functor form.
Definition TFunction.h:152
std::bool_constant< CConstCallableMethodForm< Func, Class, R, Args... > > TIsConstCallableMethod
Test if the target Func is a method and is invocable with a const object. Note that the test is for w...
Definition TFunction.h:117
std::bool_constant< CFreeFunctionForm< Func, R, Args... > > TIsFreeFunction
Main callable target traits. Test whether the target is of specific form and is invocable using Args ...
Definition TFunction.h:110
TFunction(std::nullptr_t)
Creates an invalid function that cannot be called.
Definition TFunction.h:163
TFunction & set()
Set an empty functor or lambda without capture.
Definition TFunction.h:249
std::bool_constant< sizeof(std::decay_t< Func >)<=BUFFER_SIZE && alignof(std::decay_t< Func >)<=BUFFER_ALIGNMENT > TCanFitBuffer
Check whether the type Func is small enough to be stored in the internal buffer.
Definition TFunction.h:98
std::bool_constant< CEmptyFunctorForm< Func, R, Args... > > TIsEmptyFunctor
Check if the type Func is a functor type without member variable. The type Func must also be default-...
Definition TFunction.h:133
TFunction & set(Func &&func)
Set and store a functor or lambda with captures.
Definition TFunction.h:270
Definition TFunction.h:23
The root for all renderer implementations.
Definition EEngineProject.h:6
function_detail::TFunction< Func, MIN_SIZE_HINT > TFunction
Definition TFunction.h:414