2310C++ callback to waiter

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 additionallyMergeit.

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,parameter list after code>splicing is used to call the 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 parameter list of the callback interface, and then pass it to the callback 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 adapter from external Function selector, this design allows the adapter 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 functionReplaced 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 adaptation . In this way, the 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>.

syntaxbug.com © 2021 All Rights Reserved.