original
This article uses metaprogramming
technology to automatically adapt the callback
interface function and convert it into a waiter
object.
Usage example:
name callback {<!-- --> Use schedule = list<function<empty()>>; NullAdd(Scheduling&_Scheduling, Int&&_a, Constant&_b, Double_c, Double_d, Function<null(Integer, Double)>_f){< !-- --> _Scheduling.Original ([_f, x = _a + _b, y = _c + _d]{<!-- -->_f(x, y);}); } empty ride(scheduling &_scheduling, function <empty(size, size)> _f, integer _a, integer _b, size _c){<!-- --> _Scheduling.Original ([_f, x = _a * _b, y = _c]{<!-- -->_f(x, y);}); } void repeat(scheduling &_scheduling, function<null(size)> _im, function<null(size)> _f, size_e){<!-- --> _im(_e * 2); _Scheduling.Original ([_f, _e]{<!-- -->_f(_e * 3);}); } } name coroutine {<!-- --> structure commitment; Construct coroutine: coroutine<promise>{<!-- -->use promise type = construct promise;}; Structure Commitment{<!-- --> Coroutine fetch(){<!-- -->中 {<!-- -->Coroutine::from promise(*this)};} Never hang. Initial hang() is always the same as {<!-- -->in {<!-- -->};} Never hang, eventually hang (), always the same as {<!-- -->in {<!-- -->};} Empty empty() is the same as {<!-- -->} Null is no different from Exception() Constant {<!-- -->} }; } Integer main (integer _number of parameters, symbol * _parameter value[]) {<!-- --> //Task queue. callback::schedule schedule; static routine dynamic im = [](dynamic){<!-- -->output << "[immediate]" << end of line;}; //Small callback hell. constant whole reference = 257; callback::add( Scheduling, 257, Reference, 57000.0f, 57000.0f, [ & amp;Scheduling](moving _x, moving _y){<!-- --> Output << "Callback<add>: " << _x + _y << end of line; Constant integer e = _x + _y; callback::multiply( scheduling, [& amp;Scheduling, e](moving _x, moving _y){<!-- --> Output << "callback<multiply>: " << e + _x * _y << end of line; callback::repeat(scheduling, im, [](action _x){<!-- -->output << "callback<repeat>: " << _x <<end of line;}, 177); }, 2, 8, 112831 ); } ); //coroutine. [ & amp;Scheduling, & amp;Reference]() -> Coroutine::Coroutine {<!-- --> Constant integer e = application( [](animate _x, animate _y){<!-- -->Output << "coroutine<add>: " << _x + _y << end of line; middle _x + _y;}, wait adapter::make<callback::add>::awaitable(scheduling, 257, reference, 57000.0f, 57000.0f) ); application( [e](action _x, action _y){<!-- -->Output << "Coroutine<multiplication>: " << e + _x * _y << end of line;}, await adapter::make<callback::multiply>::awaitable(dispatch, 2, 8, 112831) ); application( [](animated _x){<!-- -->Output << "Coroutine<repeat>: " << _x << end of line;}, await adapter::make<callback::repeat, adapter::select<2>>::awaitable(scheduling, im, 177) ); }(); //Call the task queue. when(dispatch.size()) Output << "[Queue.Size() = " << Scheduling.Size() << "] ", Scheduling.Previous()(), Scheduling.BounceBefore(); medium 0; } Output: [queue.size() = 2] callback<add>: 114514 [queue.size() = 2] coroutine<add>: 114514 [queue.size() = 2] callback<multiply>: 1919810 [immediately] [queue.size() = 2] coroutine<multiply>: 1919810 [immediately] [queue.size() = 2] callback<repeat>: 531 [queue.size() = 1] coroutine<repeat>: 531
Terminology convention
Callback::Add(...), Callback::Multiply(...)
interface and Callback::Repeat(...)
are called Callback interface
.
In its parameter list
, the type
is the parameter _f
of function<...>
, which is called in this article >Callback function
.
Requirements analysis
Perform flow analysis
Both callback
and coroutine
involve the stopping and starting
of the state machine.
A. The callback interface
call corresponds to the assistant
symbol, both of which are the stop and restart points
of the state machine.
1. For callback interface
calls, the first half
of its state machine is the function process
before the call, and the second half is the callback function
2. For the Assistant
symbol, the first half
of its state machine is the coroutine process
before it, and the second half is the after it. >Coroutine process
.
B. Execution flow control
of the second half
part of the state machine. Both closures
are callable entities
>.
1. For callback interface
, it is callback function
.
2. For coroutine
, it is the suspended coroutine (_h)
of the coroutine
received through the parameter.
The callback interface
provides the callback execution flow
, so it needs to be packaged by the association handle
wrapper association handle
, Replace
the callback function
originally received by the callback interface
, and the second half
part of the coroutine’s state machine is indirectly forwarded
code>gives callback interface
.
The coroutine
wrapper is usually a simple λ
formula. At the same time, the assistant
symbol inside the coroutine
is required Then place the replaced
original callback function
and call it according to the return value parameter
of the Assistant
style, that is This process allows you to expand the callback nesting
.
Note that adaptation
from callback
to waiter
cannot cover
all.
The Callback
interface can produce Fork
semantics, but Coroutine
cannot.
1. Therefore, only coroutine
can be used to represent callback
called by chain
2. If you use the Assistant
symbol of Serial
to simulate the Parallel
callback interface, you cannot Parallel
3. For the parallel
callback interface, an additional concurrent waiter
wrapper must be constructed. The content of this article
does not include this technology
4. Even if concurrency adaptation
has fork
semantics callback interface
call, coroutine
will have to additionallyMerge
it.
Data flow analysis
The coroutine
needs to stop first
before it can transfer control
to the waiter
.
Because the coroutine
wrapper needs to encapsulate the coroutine
, and the latter
must be stopped after the coroutine state machine
, can be taken by waiter
, so waiter
must be temporarily stored before
and passed in by coroutine
Other callback interface parameters
.
After that, after the coroutine stops
, pass the coroutine handle
into the suspend coroutine
and press the coroutine handle
wrapper Bind
it. In Pending Association
, and splice the Association Handle
wrapper and Temporary Parameters
,callback interface
.
This temporary parameter
operation is equivalent to currying
, if the coroutine
object itself needs to be reverse-injected into the waiter
Dependency (such as providing distributor
) can also be achieved through currying
(this article does not demonstrate it, and requires the help of the conversion
interface protocol ).
Except for timers
and no parameters
event notifications, almost all callback functions
need to receive asynchronous information
.
For example, the receive
asynchronous callback often receives the network data stream or error code
that the operating system has obtained through its parameter
.
In this article, the asynchronous information
passed to the callback function
is called callback result
.
The waiter
will be notified through the parameters of the callback function
(that is, the association handle
wrapper) after binding the callback result
Object. The callback result will be temporarily stored
inside the waiter
object, and will then be restored
by the coroutine
wrapper coroutine environment, after restoring the coroutine
, the waiter
object immediately passes the callback result
just obtained through the recovery coroutine
to the coroutine.
a. Before the execution flow is suspended
Coroutine->Parameters->Temporary parameters->Assistant->Coroutine shutdown->Pending coroutine->Coordinator->Cooperate wrapper->Splicing->Callback interface
b. After the execution flow is restored
Callback function->Callback result->Temporary result->Coroutine recovery->Restore coroutine->Result->Coroutine
Only the following can be observed inside the coroutine: Parameters->Temporary parameters->Cowait->Result
.
Adapter implementation
#Contains <io stream> #contains <tuple> #contains <list> #contains <function> #Contains <coroutine> #Contains "functor information.h" #Contains "tuple.h" #Contains "ping.h" #Contains "view.h" name adapter {<!-- --> name details {<!-- --> element<false::functor c action &_functor, false::mezzanine c _index, false::tuple c _in n, type name... _actual parameters> Construct waiter{<!-- --> Use actual parameter type = tuple<_actual parameter...>; Use medium type = remove common reference type <_中n>; Waiter(_actual parameter..._actual parameter): actual parameter{<!-- -->forward<_actual parameter>(_actual parameter)...}{<!-- -->} static regular extreme direct association () no different {<!-- -->medium false;} Empty pending association (action _h){<!-- --> [this, _h]<size... _index>(citation <_index...>){<!-- --> Constant moving each = [this, _h](false::mezzanine c moving _i) -> Derivation (moving) {<!-- --> Such as regular(_i.value < _index::value) Get <_i.value>(move(actual parameter)); Such as regular(_i.value == _index::value) Zhong [本, _h] (verb & amp; & amp;..._中n){<!-- -->中n = {<!-- -->Forward <derivation (_中n)> (_中n)...}, _h();}; Such as regular(_i.value > _index::value) Get <_i.value - 1>(move(actual parameter)); }; apply(_functor, by-tupleforward(each(false::mezzaninev<_index>)...)); }(The size of the index<...(_actual parameter) + 1>()); } Medium type restore association(){<!-- -->medium move(中n);} private: Actual parameter type Actual parameter; Medium type Medium n; }; Inline regular motion detector = [](false::package c motion_package){<!-- --> in false::functor c<false::with type<_package.echo>>;} ; element<false::functor c action &_functor, action_detector> Structure implementation {<!-- --> Static constant evaluation dynamic analysis () is the same as {<!-- --> Use status = false::query< false::partner<false::viewtype<"index">, tuple<>>, False::Partner<false::ViewType<"中n">, Tuple<>>, false::partner<false::view type<"actual parameters">, tuple<>> >; Regular action each = [](false::package_status, false::mezzanine_index, false::package_type){<!-- --> with status = false::band type<_status.echo>; Use type = false::with type<_type.echo>; Regular pole is callback = [](false::mezzanine_index, false::package_type){<!-- --> Such as regular(requires{<!-- -->_detector(_type, _index);}) in _detector(_type, _index); different in _detector(_type); }(_index, _type); Such as routine (callback){<!-- --> Use index = to remove the easy-to-reference <derivation(_index)>; Use result = typename false::functorinfo<remove constant-reference type<type>>::tuple; Use medium type = false::query rebinding type <status, false::view v<"中n">, result>; Use next type = false::query rebinding type<middle type, false::view v<"index">, index>; medium false::pack v<next type>; } Different {<!-- --> Use actual parameter = to remove the easy-to-reference type<derivation(evaluation<state>()[false::viewv<"actual parameter">])>; Use storage = conditional< is an rvalue reference value<type>, Remove reference<type>, type >; Use actual parameter = false::tuple::prototype<actual parameter, storage>; Use actual parameter type = false::query rebinding type <status, false::view v<"actual parameter">, actual parameter>; medium false::package v<actual parameter type>; } }; Use callback_actual parameter = type name false::functor information<derivation(_functor)>::tuple; with result = false::tuple::each type<callback_arguments, status, each>; medium false::pack v<result>; } Static constant evaluation dynamic creation (false::package c dynamic _package) is the same as {<!-- --> with status = false::bandtype<_pack.echo>; Use index = to remove the easy-to-reference type <derivation(evaluation<status>()[false::viewv<"index">])>; Use n = to remove the easy-to-reference type <derivation(evaluation<state>()[false::view v<"n">])>; Use actual parameter = to remove the easy-to-reference type<derivation(evaluation<state>()[false::viewv<"actual parameter">])>; Use callback_actual parameter = type name false::functor information<derivation(_functor)>::tuple; Quiet(tuple size value<callback_actual parameter> == tuple size value<actual parameter> + 1, "Detector failed"); Medium []<type name..._actual parameters>(false::type package<tuple<_actual parameters...>>){<!-- --> medium false::pack v<waiter<_functor, index, medium n, _actual parameters...>>; }(false::pack v<actual parameter>); } Use metainfo = false::withType<Analyze()>; Use type = false::with type<make(false::pack v<metainformation>)>; }; } element<false::functorc action &_functor, action_detector = detail::detector> Construct {<!-- -->use awaitable = type name details::implementation<_functor, _detector>::type;}; element<size_index> Inline routine action selection = [](action, false::mezzanine c action _i){<!-- -->in _i.value == _index;}; }
Resolving callback interfaces through metaprogramming
Because the parameters of callback interface
include callback function and other parameters
, while the parameters of waiter
only include other parameters
, so Use metaprogramming
to analyze all parameter types
of callback interface
, find out the location of callback function
, and pass Reorganize all parameters by tuple forward(...)
function, insert association handle
wrapper according to new callback function
, to in the
, and then pass it to the parameter list
of the callback interfacecallback interface
through the application(_f,_t)
interface.
The example uses the template metaprogramming toolset
in the false
library here.
A, instantiate Waiter
metaclass (Adapter::Details::Waiter<_functor,_index,_中n,_actual parameters...>)
The expected compile-time information
is as follows:
1. Reference to the callback interface _functor
(which is a compile-time or connection-time
constant)
2. The _index
position of the callback function
in the parameter list
of the callback interface.
3. The callback function parameter is the return value
of n type in assistant
formula _
4. Callback interface
all other parameters
except callback function
, whose type is _actual parameter...
The parameter type of callable
can be parsed through false::functor information<...>
a,_functor
1. This parameter is passed directly by the user, that is, the callback interface. The function of the adapter is to automatically convert
into waiter
b,_index
1. By traversing the callback interface
parameter list, find the parameters whose type
is callback function
2. The _detector
meta-parameter specifies
the discrimination condition
of the callback function type.
3. _Detector
is a λ
formula that encapsulates limitation
. It receives type
and returns pole
4. The default _detector
selector treats the callable
type according to the callback function
5. false::sandwich type
is used to save compile-time numeric constants
. Its function is similar to integer constants
c,_中n
1. During the process of traversing the callback interface parameter list
, once the identification
comes out of the callback function
, record the later
Parameter type list
.
2. The callback function
receives the callback result
through parameters
and converts it into a tuple<...>
, that is Return type of Waiter
d,_actual parameter...
Through the false::tuple::prototype<...>
tool record
the except for the
.callback function
in the callback interface >Other parameter types
Among the compile-time information
required to instantiate the waiter
template class, the last three items must be in the traversal
callback interface's parameter list
Parsing and recording
in code>. Because template metaprogramming
is stateless
, you need to manually construct a state machine
, and simulate the iterative
process through recursion
.
The recursive implementation is encapsulated
in the false::tuple::each type<...>
tool. In the example, it is analyzed using a syntax similar to iterative
Parameter list of callback interface
.
Each parameter of the callback interface
is passed to each
of the λ
formula through the false::typepack<_T>
typepack >.
When iterating, use the _status
parameter to record compile-time information
, which is a packaging type of false::query<...>
element Type package
for instances.
False::The query<...>
type is a compile-time dictionary
. When iterating, modify the type of its value
to compile-time
Record type. Use the false::query rebinding<...>
tool to replace the value
in false::query
>Type.
The parameters
are the original false::query
type, the key
type and the value
type to be set.
The key
type in this example is false::view<_symbol...>
type of compile-time string type
, if False::View
stores different strings, and its meta-instance
types are also different. Its variable-length template parameters
list is the string itself
code>.
The _detector
parameter objectifies the restriction
function, allowing the callback to be set for the
, this design allows the adapter
from external
Function selectoradapter
to automatically adapt to the callback interface parameter order
.
But in the worst case, if there are other parameter types
in the parameter list of the callback interface
that are too similar to the callback function
, so that the callback function selectorIf you can't
distinguish between the two, you can pass in Adapter::Select <_index>
to force specify
, callback function
position
in the callback interface parameter list.
Pay attention to the reference type
of the parameter. You should remove the rvalue reference
and move it when the waiter
temporarily stores the parameter.
Temporarily store the parameters and results in Waiter
In this example, it is assumed that the waiter
is once, so the callback
uses move
to move the data throughout the process.
When parsing the callback interface
, the temporary callback
parameters and callback result
have been collected through metaprogramming
Tuple<...> type.
In the constructor
of the Waiter
object, temporarily store
the callback parameters and directly forward (forward)
to The
.actual parameters
member of waiter
After that, you need to splice it and the coroutine handle
wrapper in suspension coroutine
, because at this time, coroutine control
will pass in Associate handle
.
All parameters of the callback interface
include the parameters and the callback function
temporarily stored in the actual parameters
. You need to replace the original callback function
Replaced with the Contact handle
wrapper, when callback event
occurs, the callback result
is notified to the waiter
, and then restore the coroutine
environment.
Contact handler
wrapper:
as regular(_i.value == _index::value) Zhong [本, _h] (verb & amp; & amp;..._中n){<!-- -->中n = {<!-- -->Forward <derivation (_中n)> (_中n)...}, _h();};
The callback result is temporarily stored
inside the Co-handle
wrapper. In the implementation, the Co-handle
wrapper is used according to the callback function, and the callback event is passed in. The parameters
of the result.
Directly forward (forward)
the callback result to the n
member.
Afterwards, the Coroutine
wrapper calls the Coroutine
to restore the coroutine environment, which will cause the coroutine environment to automatically call the Restore Coroutine
interface.
By accessing the n member in of
Waiter
, pass the callback result
that was previously temporarily stored
to the association Cheng
.
Because tuple
is used to pass the callback result, you can directly use application(_f,_t)
to call the original callback function before
. In this way, the adaptation
nested callback interface
calls the adaptation
smoothly into the assistant
method.
Technical defects
1. It cannot adapt to the generic callback interface
gracefully. If the interface is generic
, you need to manually meta-specialize
.
2. The callback
method with fork
semantics cannot be adapted because the coroutine
itself only contains one execution flow
code>.