forked from Sneeds-Feed-and-Seed/sneedacity
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAttachedVirtualFunction.h
290 lines (255 loc) · 9.84 KB
/
AttachedVirtualFunction.h
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
/*!********************************************************************
Sneedacity: A Digital Audio Editor
@file AttachedVirtualFunction.h
@brief Utility for non-intrusive definition of a new method on a base class
Paul Licameli
**********************************************************************/
#ifndef __SNEEDACITY_ATTACHED_VIRTUAL_FUNCTION__
#define __SNEEDACITY_ATTACHED_VIRTUAL_FUNCTION__
#include <functional>
#include <mutex>
#include <type_traits>
#include <utility>
#include "InconsistencyException.h"
//! Class template generates single-dispatch, open method registry tables
/*!
Defines a "virtual" function with multiple bodies chosen by type-switch
on the runtime class of the first argument, leaving the set of overrides
open-ended for extension, but also requiring no modification of the definition
of the base class of the type hierarchy that is switched on.
The invocation of the function is not as efficient as for true virtual functions
but the advantage of this utility is greater compilation decoupling. Client code
can attach its own virtual functions, non-intrusively, to the root of a class hierarchy
defined in the core.
There is no collection of overriding function pointers into virtual function tables by the
linker, but a registration of overrides into a table at static initialization time. This allows
those implementations to be defined wherever is convenient, even in dynamically loaded
libraries.
Beware that invocation of the function should not be done during initialization
of file scope static objects. Dispatch might not go to the correct subclass
case if initializations are not yet complete.
Example usage:
A core class:
```
// file Host.h
class AbstractHost
{
// ...
};
```
Declare the attached function
(in a header that Host.cpp need not include at all)
as a specialization of the template:
```
// file Client.h
enum class ErrorCode { Ok, Bad, // ...
}; // a return type for our function
// First this empty structure serving just to distinguish among instantiations
// of AttachedVirtualFunction with otherwise identical parameters
// An incomplete type is enough
struct DoSomethingTag;
// Now declare the "virtual function"
using DoSomething =
AttachedVirtualFunction<
DoSomethingTag,
ErrorCode,
AbstractHost, // class to be switched on by runtime type
int, double // other arguments
>;
```
Definitions needed:
```
//file Client.cpp
// Define the default function body here (as a function returning a function!)
template<> auto DoSomething::Implementation() -> Function {
return [](AbstractHost &host, int arg1, double arg2) {
return ErrorCode::Ok;
};
// or you could return nullptr instead of a lambda to force InconsistencyException
// at runtime if the virtual function is invoked for a host subclass for which no override
// was defined.
}
// Must also guarantee construction of an instance of class DoSomething at least
// once before any use of DoSomething::Call()
static DoSomething registerMe;
```
Usage of the method somewhere else:
```
#include "Client.h"
void UseDoSomething( AbstractHost &host )
{
// ...
auto error = DoSomething::Call( host, 0, 1.0 );
// ...
}
```
Derived classes from AbstractHost, not needing Client.h:
```
// file SpecialHost.h
#include "Host.h"
class SpecialHost : public AbstractHost
{
// ...
};
class ExtraSpecialHost : public SpecialHost
{
// ...
};
```
Overrides of the method, defined in any other .cpp file:
```
#include "SpecialHost.h"
#include "Client.h"
// An override of the function, building up a hierarchy of function bodies parallel
// to the host class hierarchy
using DoSomethingSpecial = DoSomething::Override< SpecialHost >;
template<> template<> auto DoSomethingSpecial::Implementation() -> Function {
// The function can be defined without casting the first argument
return [](SpecialHost &host, int arg1, double arg2) {
return arg1 == 0 ? ErrorCode::Ok : ErrorCode::Bad;
};
}
static DoSomethingSpecial registerMe;
// A further override, demonstrating call-through too
using DoSomethingExtraSpecial =
DoSomething::Override< ExtraSpecialHost, DoSomethingSpecial >;
template<> template<>
auto DoSomethingExtraSpecial::Implementation() -> Function {
return [](ExtraSpecialHost &host, int arg1, double arg2){
// Call the immediately overridden version of the function
auto result = Callthrough( host, arg1, arg2 );
if ( result == ErrorCode::OK ) {
if ( arg2 != 1066.0 )
result = ErrorCode::Bad;
}
return result;
};
}
static DoSomethingExtraSpecial registerMe;
```
@tparam Tag an incomplete type, to distinguish methods with otherwise identical parameters
@tparam Return the value returned by each override
@tparam This type of the first argument, a class with at least one true virtual function, the root of the hierarchy for the run-time type-switch
@tparam Arguments any number of types for the second and later arguments
*/
template< typename Tag, typename Return, typename This, typename... Arguments >
class AttachedVirtualFunction
{
public:
//! This member name is declared in this class template and redeclared in each override
using Object = This;
//! This member name is declared in this class template and redeclared in each override
using Function = std::function< Return( Object&, Arguments... ) >;
//! A function returning a std::function, which you must define so that the program links
/*! It may return nullptr in case this must act somewhat as a "pure virtual",
throwing InconsistencyException if the function is invoked on a subclass
for which no override was defined */
static Function Implementation();
//! At least one static instance must be created; more instances are harmless
/*! (There will be others if there are any overrides.) */
AttachedVirtualFunction()
{
static std::once_flag flag;
std::call_once( flag, []{ Register<This>( Implementation() ); } );
}
//! For defining overrides of the method
/*!
@tparam Subclass the more specific subclass of @b This
@tparam Overridden The immediately overridden version, defaulting to the base version
*/
template<
typename Subclass, typename Overridden = AttachedVirtualFunction >
struct Override : Overridden
{
//! Shadowing Overridden::Object
using Object = Subclass;
//! Shadowing Overridden::Function, giving the first argument a more specific type
using Function = std::function< Return( Object&, Arguments... ) >;
// Check that inheritance is correct
static_assert(
std::is_base_of< typename Overridden::Object, Object >::value,
"overridden class must be a base of the overriding class"
);
//! A function returning a std::function that must be defined so that the program links
static Function Implementation();
//! May be used in the body of the overriding function, defining it in terms of the overridden one
static Return Callthrough(
typename Overridden::Object &object, Arguments &&...arguments )
{
return Overridden::Implementation()(
object, std::forward< Arguments >( arguments )... );
}
//! At least one static instance must be created; more instances are harmless
/*! (There will be others if there are any further overrides.) */
Override()
{
static std::once_flag flag;
std::call_once( flag, []{
// Register in the table an adaptor thunk that downcasts the object
auto implementation = Implementation();
Register< Subclass >( [=]( This &obj, Arguments &&...arguments ){
return implementation(
static_cast< Subclass& >( obj ),
std::forward< Arguments >( arguments )... );
});
});
}
};
//! Invoke the method -- but only after static initialization time
static Return Call(
This &obj, //!< Object on which to type-switch at run-time
Arguments &&...arguments //!< other arguments
)
{
try {
// Note that the constructors of this class and overrides cause
// the registry to be topologically sorted, with functions for
// less-derived classes earlier in the table; so take the last
// one that matches the object. (The object might not be of the exact
// class corresponding to any of the overrides, which is why this
// solution involves calling the predicates generated in Register,
// and wouldn't work just with hashing on std::type_index; but perhaps
// such a cache could be memo-ized)
auto ®istry = GetRegistry();
auto iter = registry.rbegin(), end = registry.rend();
for ( ; iter != end; ++iter ) {
auto &entry = *iter;
if ( entry.predicate( &obj ) )
// This might throw std::bad_function_call on a null function
return entry.function(
obj, std::forward< Arguments >( arguments )... );
}
// If not found, also throw
throw std::bad_function_call{};
}
catch ( const std::bad_function_call& ) {
// No matching case found with a non-null function.
// Translate the exception
THROW_INCONSISTENCY_EXCEPTION;
}
}
private:
template< typename Subclass >
static void Register( const Function &function )
{
// Push back a dynamic type test and corresponding function body
GetRegistry().push_back({
[]( This *b ){ return dynamic_cast< Subclass * >( b ) != nullptr; },
function
});
}
using Predicate = std::function< bool( This* ) >;
//! Member of registry of implementations of the method
struct Entry
{
Predicate predicate;
Function function;
};
using Registry = std::vector< Entry >;
static Registry &GetRegistry()
{
static Registry registry;
return registry;
}
};
#endif