-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfunction_ref.hpp
159 lines (138 loc) · 6.46 KB
/
function_ref.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
* Copyright (C) 2018 Giel van Schijndel
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If
* not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <cassert>
#include <cstddef>
#include <type_traits>
#include <utility>
// Based on P0792R2, so following its naming exactly
namespace utils
{
namespace detail
{
// Pointer to different kinds of invocable. Either functions or functors.
union invocable_ptr
{
constexpr invocable_ptr() noexcept = default;
constexpr invocable_ptr(void* p) noexcept : optr{p} {}
constexpr invocable_ptr(void (*fp)()) noexcept : fptr{fp} {}
// We're storing function pointers and object pointers (for functors) separately, because as a
// result of [expr.reinterpret.cast], paragraph 8, support for conversion between these classes of
// pointers is implementation dependent (not implementation-defined, if supported it's meaning
// is specified, but it's only optionally supported).
void* optr = nullptr;
void (* fptr)();
};
template <typename Signature>
struct function_classification;
template <typename R, typename... Args>
struct function_classification<R(Args...)> {
static constexpr bool is_noexcept = false;
using signature = R(Args...);
};
// noexcept became part of the type system since C++17
template <typename R, typename... Args>
struct function_classification<R(Args...) noexcept> {
static constexpr bool is_noexcept = true;
using signature = R(Args...);
};
} // namespace detail
template <
typename Signature
, typename = typename detail::function_classification<Signature>::signature>
class function_ref;
template <typename Signature, typename R, typename... Args>
class function_ref<Signature, R(Args...)>
{
private:
using class_t = detail::function_classification<Signature>;
static constexpr bool is_noexcept = class_t::is_noexcept;
// NOTE: for best performance on systems that follow the Itanium ABI this struct should have no
// more than these two members and in this order. Specifically structs with a maximum of
// two scalar members, each no larger than a pointer, get loaded into registers instead of
// passed on the stack. This allows calls of function_ref objects to frequently avoid
// touching memory. Additionally the order is important because _invocable is the first
// argument given to _erased_func. As a result when function_ref is the first argument to
// a (static or non-member) function its already preloaded in the right register.
detail::invocable_ptr _invocable;
R (*_erased_func)(detail::invocable_ptr, Args...) noexcept(is_noexcept) = nullptr;
public:
constexpr function_ref() noexcept = default;
constexpr function_ref(std::nullptr_t) noexcept {}
template <typename F
, std::enable_if_t<
!std::is_same<std::decay_t<F>, function_ref>::value
&& std::is_function<std::remove_reference_t<F>>::value
&& std::is_invocable_r_v<R, F&, Args...>
>* = nullptr>
explicit(is_noexcept && !std::is_nothrow_invocable_r_v<R, F&, Args...>)
constexpr function_ref(F&& f) noexcept
: _invocable{reinterpret_cast<void(*)()>(std::addressof(f))}
, _erased_func{[](detail::invocable_ptr ptr, Args... args) noexcept(is_noexcept) -> R {
// relies on [expr.reinterpret.cast], paragraph 6:
// > A function pointer can be explicitly converted to a function pointer of a different
// > type. Except that converting a prvalue of type "pointer to T1" to the type "pointer to
// > T2" (where T1 and T2 are function types) and back to its original type yields the
// > original pointer value, the result of such a pointer conversion is unspecified.
//
// I.e. we're forcing a conversion to "void (*)()" as the storage type (T1), then back to a
// pointer to the original function type (T2) when calling through it.
return (*reinterpret_cast<std::add_pointer_t<F>>(ptr.fptr))(
std::forward<Args>(args)...);
}}
{
}
template <typename F
, std::enable_if_t<
!std::is_same<std::decay_t<F>, function_ref>::value
&& !std::is_function<std::remove_reference_t<F>>::value
&& std::is_invocable_r_v<R, F&, Args...>
>* = nullptr>
explicit(is_noexcept && !std::is_nothrow_invocable_r_v<R, F&, Args...>)
constexpr function_ref(F&& f) noexcept
: _invocable{const_cast<void*>(static_cast<const void*>(std::addressof(f)))}
, _erased_func{[](detail::invocable_ptr ptr, Args... args) noexcept(is_noexcept) -> R {
return (*static_cast<std::add_pointer_t<F>>(ptr.optr))(
std::forward<Args>(args)...);
}}
{
}
constexpr explicit operator bool() const noexcept
{
return _erased_func != nullptr;
}
[[gnu::always_inline]] R operator()(Args... args) const noexcept(is_noexcept)
{
assert(*this && "*this does not store a callable function target");
return _erased_func(_invocable, std::forward<Args>(args)...);
}
// Helper that exposes the guts of this class to make it easily usable in C-style callbacks.
// This avoids extra indirection & conversion overhead.
// Bit hackis right now, should probably perform some proper conversions to actually rely on defined behavior...
std::pair<void* /* c_cb_arg */, R (*)(void* c_cb_arg, Args...) noexcept(is_noexcept)> as_c_callback() const noexcept {
return {
_invocable.optr,
reinterpret_cast<R (*)(void* c_cb_arg, Args...) noexcept(is_noexcept)>(
// casting via intermediate most basic function pointer to suppress a warning about incompatible function pointer type.
reinterpret_cast<void (*)() noexcept(is_noexcept)>(_erased_func)
),
};
}
};
template <typename R, typename... Args>
function_ref(R (*)(Args...)) -> function_ref<R(Args...)>;
template <typename R, typename... Args>
function_ref(R (*)(Args...) noexcept) -> function_ref<R(Args...) noexcept>;
} // namespace utils