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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
|
/**
* @file classic_callback.h
* @author Nat Goodspeed
* @date 2016-06-21
* @brief ClassicCallback and HeapClassicCallback
*
* This header file addresses the problem of passing a method on a C++ object
* to an API that requires a classic-C function pointer. Typically such a
* callback API accepts a void* pointer along with the function pointer, and
* the function pointer signature accepts a void* parameter. The API passes
* the caller's pointer value into the callback function so it can find its
* data. In C++, there are a few ways to deal with this case:
*
* - Use a static method with correct signature. If you don't need access to a
* specific instance, that works fine.
* - Store the object statically (or store a static pointer to a non-static
* instance). As long as you only care about one instance, that works, but
* starts to get a little icky. As soon as there's more than one pertinent
* instance, fight valiantly against the temptation to stuff the instance
* pointer into a static pointer variable "just for a moment."
* - Code a static trampoline callback function that accepts the void* user
* data pointer, casts it to the appropriate class type and calls the actual
* method on that class.
*
* ClassicCallback encapsulates the last. You need only construct a
* ClassicCallback instance somewhere that will survive until the callback is
* called, binding the target C++ callable. You then call its get_callback()
* and get_userdata() methods to pass an appropriate classic-C function
* pointer and void* user data pointer, respectively, to the old-style
* callback API. get_callback() synthesizes a static trampoline function
* that casts the user data pointer and calls the bound C++ callable.
*
* $LicenseInfo:firstyear=2016&license=viewerlgpl$
* Copyright (c) 2016, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_CLASSIC_CALLBACK_H)
#define LL_CLASSIC_CALLBACK_H
#include <tuple>
#include <type_traits> // std::is_same
/*****************************************************************************
* Helpers
*****************************************************************************/
// find a type in a parameter pack: http://stackoverflow.com/q/17844867/5533635
// usage: index_of<0, sought_t, PackName...>::value
template <int idx, typename sought, typename candidate, typename ...rest>
struct index_of
{
static constexpr int const value =
std::is_same<sought, candidate>::value ?
idx : index_of<idx + 1, sought, rest...>::value;
};
// recursion tail
template <int idx, typename sought, typename candidate>
struct index_of<idx, sought, candidate>
{
static constexpr int const value =
std::is_same<sought, candidate>::value ? idx : -1;
};
/*****************************************************************************
* ClassicCallback
*****************************************************************************/
/**
* Instantiate ClassicCallback in whatever storage will persist long enough
* for the callback to be called. It holds a modern C++ callable, providing a
* static function pointer and a USERDATA (default void*) capable of being
* passed through a classic-C callback API. When the static function is called
* with that USERDATA pointer, ClassicCallback forwards the call to the bound
* C++ callable.
*
* Usage:
* @code
* // callback signature required by the API of interest
* typedef void (*callback_t)(int, const char*, void*, double);
* // old-style API that accepts a classic-C callback function pointer
* void oldAPI(callback_t callback, void* userdata);
* // but I want to pass a lambda that references data local to my function!
* // (We don't need to name the void* parameter in the C++ callable;
* // ClassicCallback already used it to locate the lambda instance.)
* auto ccb{
* makeClassicCallback<callback_t>(
* [=](int n, const char* s, void*, double f){ ... }) };
* oldAPI(ccb.get_callback(), ccb.get_userdata());
* // If the passed callback is called before oldAPI() returns, we can now
* // safely destroy ccb. If the callback might be called later, consider
* // HeapClassicCallback instead.
* @endcode
*
* If you have a callable object in hand, and you want to pass that to
* ClassicCallback, you may either consume it by passing std::move(object), or
* explicitly specify a reference to that object type as the CALLABLE template
* parameter:
* @code
* CallableObject obj;
* ClassicCallback<callback_t, void*, CallableObject&> ccb{obj};
* @endcode
*/
// CALLABLE should either be deduced, e.g. by makeClassicCallback(), or
// specified explicitly. Its default type is meaningless, coded only so we can
// provide a useful default for USERDATA.
template <typename SIGNATURE, typename USERDATA=void*, typename CALLABLE=void(*)()>
class ClassicCallback
{
typedef ClassicCallback<SIGNATURE, USERDATA, CALLABLE> self_t;
public:
/// ClassicCallback binds any modern C++ callable.
ClassicCallback(CALLABLE&& callable):
mCallable(std::forward<CALLABLE>(callable))
{}
/**
* ClassicCallback must not itself be copied or moved! Once you've passed
* get_userdata() to some API, this object MUST remain at that address.
*/
// However, we can't yet count on C++17 Class Template Argument Deduction,
// which means makeClassicCallback() is still useful, which means we MUST
// be able to return one to construct into caller's instance (move ctor).
// Possible defense: bool 'referenced' data member set by get_userdata(),
// with an llassert_always(! referenced) check in the move constructor.
ClassicCallback(ClassicCallback const&) = delete;
ClassicCallback(ClassicCallback&&) = default; // delete;
ClassicCallback& operator=(ClassicCallback const&) = delete;
ClassicCallback& operator=(ClassicCallback&&) = delete;
/// Call get_callback() to get the necessary function pointer.
SIGNATURE get_callback() const
{
// This declaration is where the compiler instantiates the correct
// signature for the call() function template.
SIGNATURE callback = call;
return callback;
}
/// Call get_userdata() to get the opaque USERDATA pointer to pass
/// through the classic-C callback API.
USERDATA get_userdata() const
{
// The USERDATA userdata is of course a pointer to this object.
return static_cast<USERDATA>(const_cast<self_t*>(this));
}
protected:
/**
* This call() method accepts one or more callback arguments. It assumes
* the first USERDATA parameter is the userdata.
*/
// Note that we're not literally using C++ perfect forwarding here -- it
// doesn't work to specify (Args&&... args). But that's okay because we're
// dealing with a classic-C callback! It's not going to pass any move-only
// types.
template <typename... Args>
static auto call(Args... args)
{
auto userdata = extract_userdata(std::forward<Args>(args)...);
// cast the userdata param to 'this' and call mCallable
return static_cast<self_t*>(userdata)->
mCallable(std::forward<Args>(args)...);
}
template <typename... Args>
static USERDATA extract_userdata(Args... args)
{
// Search for the first USERDATA parameter type, then extract that pointer.
// extract value from parameter pack: http://stackoverflow.com/a/24710433/5533635
return std::get<index_of<0, USERDATA, Args...>::value>(std::forward_as_tuple(args...));
}
CALLABLE mCallable;
};
/**
* Usage:
* @code
* auto ccb{ makeClassicCallback<classic_callback_signature>(actual_callback) };
* @endcode
*/
template <typename SIGNATURE, typename USERDATA=void*, typename CALLABLE=void(*)()>
auto makeClassicCallback(CALLABLE&& callable)
{
return std::move(ClassicCallback<SIGNATURE, USERDATA, CALLABLE>
(std::forward<CALLABLE>(callable)));
}
/*****************************************************************************
* HeapClassicCallback
*****************************************************************************/
/**
* HeapClassicCallback is like ClassicCallback, with this exception: it MUST
* be allocated on the heap because, once the callback has been called, it
* deletes itself. This addresses the problem of a callback whose lifespan
* must persist beyond the scope in which the callback API is engaged -- but
* naturally this callback must be called exactly ONCE.
*
* Usage:
* @code
* // callback signature required by the API of interest
* typedef void (*callback_t)(int, const char*, void*, double);
* // here's the old-style API
* void oldAPI(callback_t callback, void* userdata);
* // want to call someObjPtr->method() when oldAPI() fires the callback,
* // sometime in the future after the enclosing function has returned
* auto ccb{
* makeHeapClassicCallback<callback_t>(
* [someObjPtr](int n, const char* s, void*, double f)
* { someObjPtr->method(); }) };
* oldAPI(ccb.get_callback(), ccb.get_userdata());
* // We don't need a smart pointer for ccb, because it will be deleted once
* // oldAPI() calls the bound lambda. HeapClassicCallback is for when the
* // callback will be called exactly once. If the classic API might call the
* // passed callback more than once -- or might never call it at all --
* // manually construct a ClassicCallback on the heap and manage its lifespan
* // explicitly.
* @endcode
*/
template <typename SIGNATURE, typename USERDATA=void*, typename CALLABLE=void(*)()>
class HeapClassicCallback: public ClassicCallback<SIGNATURE, USERDATA, CALLABLE>
{
typedef ClassicCallback<SIGNATURE, USERDATA, CALLABLE> super;
typedef HeapClassicCallback<SIGNATURE, USERDATA, CALLABLE> self_t;
// This destructor is intentionally private to prevent allocation anywhere
// but the heap. (The Design and Evolution of C++, section 11.4.2: Control
// of Allocation)
~HeapClassicCallback() {}
public:
HeapClassicCallback(CALLABLE&& callable):
super(std::forward<CALLABLE>(callable))
{}
// makeHeapClassicCallback() only needs to return a pointer -- not an
// instance -- so we can lock down our move constructor too.
HeapClassicCallback(HeapClassicCallback&&) = delete;
/// Replicate get_callback() from the base class because we must
/// instantiate OUR call() function template.
SIGNATURE get_callback() const
{
// This declaration is where the compiler instantiates the correct
// signature for the call() function template.
SIGNATURE callback = call;
return callback;
}
/// Replicate get_userdata() from the base class because our call()
/// method must be able to reconstitute a pointer to this subclass.
USERDATA get_userdata() const
{
// The USERDATA userdata is of course a pointer to this object.
return static_cast<const USERDATA>(const_cast<self_t*>(this));
}
private:
// call() uses a helper class to delete the HeapClassicCallback when done,
// for two reasons. Most importantly, this deletes even if the callback
// throws an exception. But also, call() must directly return the callback
// result for return-type deduction.
struct Destroyer
{
Destroyer(self_t* p): mPtr(p) {}
~Destroyer() { delete mPtr; }
self_t* mPtr;
};
template <typename... Args>
static auto call(Args... args)
{
// extract userdata at this level too
USERDATA userdata = super::extract_userdata(std::forward<Args>(args)...);
// arrange to delete it when we leave by whatever means
Destroyer destroy(static_cast<self_t*>(userdata));
return super::call(std::forward<Args>(args)...);
}
};
template <typename SIGNATURE, typename USERDATA=void*, typename CALLABLE=void(*)()>
auto makeHeapClassicCallback(CALLABLE&& callable)
{
return new HeapClassicCallback<SIGNATURE, USERDATA, CALLABLE>
(std::forward<CALLABLE>(callable));
}
#endif /* ! defined(LL_CLASSIC_CALLBACK_H) */
|