Quick Links: |
Provide a signature-specific function object (functor). More...
Namespaces | |
namespace | bdlf |
Defines | |
#define | BDLF_BIND_PARAMINDEX(N) |
#define | BDLF_BIND_EVAL(N) |
bdlf::Bind | select the proper implementation for the binder |
bdlf::BindWrapper | reference counted function binder |
bdlf::BindUtil | factory methods for creating the binder |
bdlf::Bind
, that is a functor object that binds an invocable object or function to a list of arguments. This component also defines factory methods in the bdlf::BindUtil
namespace for creating bdlf::Bind
objects (e.g., bind
and bindR
) and bdlf::BindWrapper
objects (e.g., bindS
and bindSR
). The bdlf::Bind
functor (called henceforth a "binder") is an object that can hold any invocable object (the "bound functor") and a number of parameters (the "bound arguments", some of which can be place-holders of type bdlf::PlaceHolder
). When the binder is later invoked (with optional additional arguments called the "invocation
arguments" used to compute the value of bound arguments that use place-holders), it returns the result of calling the bound functor with the bound arguments and invocation arguments. The bdlf::BindWrapper
functor provides a binder with shared pointer semantics to a non-modifiable binder (both operator*
and operator->
accessors) and forwards its construction arguments to the underlying binder. The section "Supported functionality
and limitations" details which kind of bound functors and bound arguments can be used in conjunction with this component. bdlf::Bind
is to invoke bound functors with additional data that is not available prior to the point the functor is invoked (as is often the case with callback functions). The binding is accomplished by passing place-holders instead of literal values as the bound arguments. When the binder is invoked, these place-holders are replaced by their respective invocation arguments. The section "Elementary construction and
usage of <code>bdlf::Bind</code> objects" shows an elementary (but not realistic) usage of the bdlf::BindUtil::bind
factory methods with free functions, that should be enough for most users to grasp the basic usage of this component. The section "Binding data" offers more details and should enable a user to make a more advanced usage of this component. The usage example presents many uses of bdlf::Bind
and bdlf::BindUtil
in a somewhat realistic (but a bit more involved) situation. bdlf::Bind
functors are typically used with standard algorithms, or with bsl::function
. This mechanism is similar to, but much more powerful than, bsl::bind1st
or bsl::bind2nd
. bindS
and bindSR
and a binder created using bind
, and bindR
is that in the former case the binder is returned by reference rather than by value, with shared ownership semantics. Hence its main use is for creating binders that hold a non-trivial amount of storage (i.e., the bound arguments) and will be copied, possibly several times, such as jobs enqueued in a threadpool. operator()
method. This component supports bound objects that can be function pointers, member function pointers (the first bound argument must evaluate to an instance of the class of which the function is a member), or function objects passed by address or by value. In addition, there is a limitation on the number of parameters that such an object can take (currently no more than 14). bdlf::Bind
functor can be constructed, usually by one of the bdlf::BindUtil
factory methods, from a bound functor and from 0 up to 14 bound arguments that can be either literal values, place-holders of type bdlf::PlaceHolder
, or further bdlf::Bind
functors. The type of a binder object is a complicated expression, which is why a binder is typically a short-lived object that is returned or passed as parameter to a function template. That is also why the bdlf::BindUtil
factory methods are the preferred way to create a binder. When a binder is later invoked with some arguments, literal values are passed to the bound functor and place-holders are substituted by their respective invocation arguments. In addition, any argument that is a binder itself is invoked recursively with the same invocation arguments and the result of that invocation is passed to the parent binder. The section "Elementary construction and usage of
<code>bdlf::Bind</code> objects" below details the whole mechanism and offers some examples. bdlf::Bind
functors support bslma::Allocator *
arguments. When binders are constructed by the bdlf::BindUtil::bind
(and bindR
) factory methods, the currently installed default allocator is used. When binders are constructed by the bdlf::BindUtil::bindS
(and bindSR
) factory methods, the non-optional, user-supplied allocator is used both for the creation of the bound functor arguments and for the reference counting mechanism that manages those arguments. See the section "Binding with
allocators" below for a more detailed discussion. bdlf::BindUtil
with an "invocation template". An invocation template is a series of one or more arguments that describe how to invoke the bound functor. Each argument can be either a place-holder or a literal value. Literal values are stored by value in the binder and directly forwarded to the bound functor when invoked. Place-holders are substituted with the respective argument provided at invocation of the binder. For example, given the following invocable
(here a free function for simplicity): void invocable(int i, int j, const char *str) { // Do something with 'i', 'j' and 'str'. printf("Invoked with: %d %d %s\n", i, j, str); }
const char *someString = "p3"; // for third parameter to 'invocable'
invocable
to the following arguments: void bindTest(bslma::Allocator *allocator = 0) { bdlf::BindUtil::bind(&invocable, // bound functor and 10, 14, someString) // bound arguments
(); // invocation
bindS
method: bdlf::BindUtil::bindS(allocator, // allocator, &invocable, // bound object and 10, 14, (const char*)"p3")(); // bound arguments }
invocable
will be bound with the arguments 10
, 14
, and "p3"
respectively, then invoked with those bound arguments. In the next example, place-holders are used to forward user-provided arguments to the bound functor. We separate the invocation of the binder into a function template to avoid having to declare the type of the binder: template <class BINDER> void callBinder(BINDER const& b) { b(10, 14); }
void bindTest1(bslma::Allocator *allocator = 0) { callBinder(bdlf::BindUtil::bind(&invocable, _1, _2, someString)); callBinder(bdlf::BindUtil::bindS(allocator, &invocable, _1, _2, someString)); }
callBinder
template function is invoked with a binder bound to the specified invocable
and having the invocation template _1
, _2
, and "p3"
respectively. The two special parameters _1
and _2
are place-holders for arguments one and two, respectively, which will be specified to the binder at invocation time. Each place-holder will be substituted with the corresponding positional argument when invoked. When called within the callBinder
function, invocable
will be invoked as follows: invocable(10, 14, "p3");
callBinder
function, is invoked with a binder such that argument one (10) of the binder is passed as parameter two and argument two (14) is passed as (i.e., bound to) parameter one: void bindTest2(bslma::Allocator *allocator = 0) { callBinder(bdlf::BindUtil::bind(&invocable, _2, _1, someString)); callBinder(bdlf::BindUtil::bindS(allocator, &invocable, _2, _1, someString)); }
callBinder
function, invocable
will be invoked as follows: invocable(14, 10, "p3");
bdlf::BindUtil
and their respective output: int test1(int i, int j) { return i + j; } int abs(int x) { return (x > 0) ? x : -x; } void bindTest3(bslma::Allocator *allocator = 0) { using namespace bdlf::PlaceHolders; assert(24 == bdlf::BindUtil::bind(&test1, _1, _2)(10, 14)); assert(24 == bdlf::BindUtil::bind(&test1, _1, 14)(10)); assert(24 == bdlf::BindUtil::bind(&test1, 10, _1)(14)); assert(24 == bdlf::BindUtil::bind(&test1, 10, 14)()); assert(24 == bdlf::BindUtil::bind(&test1, bdlf::BindUtil::bind(&abs,_1), 14)(-10)); assert(24 == bdlf::BindUtil::bindS(allocator, &test1, _1, _2)(10, 14)); assert(24 == bdlf::BindUtil::bindS(allocator, &test1, _1, 14)(10)); assert(24 == bdlf::BindUtil::bindS(allocator, &test1, 10, _1 )(14)); assert(24 == bdlf::BindUtil::bindS(allocator, &test1, 10, 14)()); assert(24 == bdlf::BindUtil::bindS(allocator, &test1, bdlf::BindUtil::bindS(allocator, &abs, _1), 14)(-10)); }
bdlf::Bind
is to invoke bound functors with additional data that is not available prior to the point the functor is invoked (as is often the case with callback functions). For that purpose, place-holders are key. There are a couple of issues to understand in order to properly use this component. The bound arguments must be of a value-semantic type (unless they are place-holders or bdlf::Bind
objects). They are evaluated at binding time once and only once and their value copied into the binder (using the default allocator to supply memory unless an allocator is specified). A bdlf::Bind
object always invokes its bound functor with only the arguments listed as bound arguments, regardless of how many arguments are specified to the binder at invocation time. Invocation arguments that are not referenced through a place-holder are simply discarded. Invocation arguments that are duplicated (by using the same place-holder several times) are simply copied several times. The following examples should make things perfectly clear. int marker = 0; int singleArgumentFunction(int x) { return x; } int identityFunctionWithSideEffects(int x) { printf("Calling 'identityFunctionWithSideEffects' with %d\n", x); marker += x; return x; } template <class BINDER> void callBinderWithSideEffects1(BINDER const& binder) { ASSERT(14 == binder(identityFunctionWithSideEffects(10), 14)); } void bindTest4(bslma::Allocator *allocator = 0) { marker = 0; callBinderWithSideEffects1(bdlf::BindUtil::bind( &singleArgumentFunction, _2));
singleArgumentFunction
will be called with only the second argument (14) specified to the binder at invocation time in the callBinderWithSideEffects1
function. Thus the return value of the invocation must be 14. The identityFunctionWithSideEffects(10)
will be evaluated, even though its return value (10) will be discarded. We can check this as follows: assert(10 == marker);
bindS
below: marker = 0; callBinderWithSideEffects1(bdlf::BindUtil::bindS( allocator, &singleArgumentFunction, _2)); assert(10 == marker); }
singleArgumentFunction
of the previous example: int doubleArgumentFunction(int x, int y) { return x+y; } template <class BINDER> void callBinderWithSideEffects2(BINDER const& binder) { const int RET1 = binder(10); ASSERT(20 == RET1); const int RET2 = binder(identityFunctionWithSideEffects(10)); ASSERT(20 == RET2); } void bindTest5(bslma::Allocator *allocator = 0) { marker = 0; callBinderWithSideEffects2(bdlf::BindUtil::bind( &doubleArgumentFunction, _1, _1));
doubleArgumentFunction
will be called with the first argument (identityFunctionWithSideEffects(10)
) specified to the binder, computed only once at invocation time. We can check this as follows: assert(10 == marker);
bindS
below: marker = 0; callBinderWithSideEffects2(bdlf::BindUtil::bindS( allocator, &doubleArgumentFunction, _1, _1)); assert(marker, 10 == marker); }
bdlf::Bind
. When the binder detects that a member function pointer was specified, it automatically wraps it in a bdlf::MemFn
object. In this case a pointer to the object must be passed as the first argument to bind, followed by the remaining arguments. See the usage example "Binding to
Member Functions" below. this
to bind. If this
is passed as the first argument to bind, then all other arguments must be passed by value or by const reference. int printf(const char*, ...
). This component does not support ellipsis in member function pointers, however. See the bindTest7
example function at the end of the usage example below. bdlf::Bind
. When the binder detects that a pointer to a function object was specified, it automatically dereferences that pointer prior to invoking the function object. The difference between the two usages is that the binder object holds a copy of the whole object or of its address only. In particular, when passing by value an object that takes an allocator, the copy held by the binder uses the default allocator if constructed by bdlf::BindUtil::bind
or bdlf::BindUtil::bindR
, not the allocator of the original object. bindS
or bindSR
instead. See the section "Binding with allocators" and the usage example sections "Binding to Function Objects" and "Binding to
Function Objects by Reference" below. operator()
) with the intent to modify the state of the function object. However, only the copy held by the binder was modified and the original function object passed to the binder was not, but this usage error went undetected. In this version, a binder cannot modifiably invoke functors held by value, and attempting to do so will trigger a compilation error. If it is desired that an invocation modifies the state of the function object, then this function object must be passed to the binder by address. bdlf::Bind
object will strive to properly and automatically resolve the signature of its bound functor between different overloads of that invocable. The signature of the bound functor is inferred from that of the bound functor and the type of the arguments either at binding or invocation time. The signature of invocables that are not function objects (i.e., free functions with C++ linkage and member functions) must be determined at binding time (in particular, overloads must be disambiguated when obtaining the address of the function). In those cases, the bound arguments will be of the corresponding type and any values passed as bound arguments (except placeholders) will be cast to the corresponding type at binding time and stored by value unless the argument type is a reference type. printf
), if a bound argument is a nested binder, or if a placeholder is used in two positions in the bound arguments. In that case, the bound arguments are stored in their own types, and cast to the corresponding argument type of the signature only when the signature is determined at invocation time. Place-holders, likewise, are not typed and will acquire the type of their corresponding invocation argument when invoked, which will be cast to the corresponding argument type of the signature. In particular, the same binder constructed with a functor and place-holders in place of the bound arguments can invoke several overloads of the operator()
of the functor, depending on the type of the invocation arguments. extern "C"
linkage. In that case, the return type has to be given explicitly to the binder. This can be done by using the bdlf::BindUtil::bindR
function. Note that all bsl::function
objects have a standard public type result_type
to assist the deduction of return type and can be used with bdlf::BindUtil::bind
. See the usage example "Binding to a Function Object with Explicit Return
Type" below. bdlf::Bind
object. There is however one limitation: if any of the arguments in the signature of the bound functor should be of a modifiable reference type, then all the invocation arguments need to be modifiable references. That is, it is not possible to pass a literal (const
) value to some argument of a bound functor when another argument expects a modifiable reference. Note that a direct call to the bound functor (without the binder) would accept such an argument. This is not a severe limitation, and the workaround is to pass instead a local modifiable variable initialized to the literal value. bdlf::Bind
object, so no memory is allocated for storing them. However, if the bound functor or bound argument's copy constructor requires memory allocation, that memory is supplied by the currently installed default allocator unless bdlf::BindUtil::bindS
(or bindSR
) method is used. In the latter cases, the non-optional, user-supplied allocator is passed to the copy constructors of the bound functor and arguments. MyString
(kept minimal here for the purpose of exposition): class MyString { // PRIVATE INSTANCE DATA bslma::Allocator *d_allocator_p; char *d_string_p; public: // TRAITS BSLMF_NESTED_TRAIT_DECLARATION(MyString, bslma::UsesBslmaAllocator); //CREATORS MyString(const char *str, bslma::Allocator *allocator = 0) : d_allocator_p(bslma::Default::allocator(allocator)) , d_string_p((char*)d_allocator_p->allocate(1 + strlen(str))) { strcpy(d_string_p, str); } MyString(MyString const& rhs, bslma::Allocator *allocator = 0) : d_allocator_p(bslma::Default::allocator(allocator)) , d_string_p((char*)d_allocator_p->allocate(1 + strlen(rhs))) { strcpy(d_string_p, rhs); } ~MyString() { d_allocator_p->deallocate(d_string_p); } // ACCESSORS operator const char*() const { return d_string_p; } };
bslma::TestAllocator
to keep track of the memory allocated: void bindTest6() { bslma::TestAllocator allocator; MyString myString((const char*)"p3", &allocator); const Int64 NUM_ALLOCS = allocator.numAllocations();
defaultAllocator
: bslma::TestAllocator defaultAllocator("Default", globalVerbose); bslma::DefaultAllocatorGuard defaultAllocatorGuard(&defaultAllocator); const Int64 NUM_DEFAULT_ALLOCS = defaultAllocator.numAllocations();
allocator
using bindS
: callBinder( bdlf::BindUtil::bindS(&allocator, &invocable, _1, _2, myString));
allocator
is be passed to its copy constructor as occurs in this example. Here allocator
is used to make the copy of myString
held by the binder. assert(NUM_ALLOCS != allocator.numAllocations()); assert(NUM_DEFAULT_ALLOCS == defaultAllocator.numAllocations()); }
bdlf::Bind
and bdlf::BindUtil
. For these examples, we will use a typical pair of event and event dispatcher classes, where the event is defined as: struct MyEvent { // Event data, for illustration purpose here: int d_value; MyEvent() : d_value(0) {} };
class MyEventDispatcher { // This class owns a callback function object that takes an 'int' and // an instance of 'MyEvent'. When the 'dispatch' method is called, it // invokes the callback with a series of events that it obtains using // its own stream of events. // PRIVATE INSTANCE DATA bsl::function<void(int, MyEvent)> d_callback; // PRIVATE MANIPULATORS int getNextEvent(MyEvent *eventBuffer) { // Create a copy of the next event in the specified 'eventBuffer' // Return 0 on success, and non-zero if no event is available. // Implementation elided // ... }
public: // CREATORS MyEventDispatcher(bsl::function<void(int, MyEvent)> const& cb) : d_callback(cb) { }
getNextEvent
: // MANIPULATORS void dispatch(int id) { MyEvent e; while (!getNextEvent(&e)) { d_callback(id, e); } } };
dispatch
the binder will be invoked with two invocation arguments, thus we may use only place-holders _1
and _2
. In the following snippet of code, the binder passes its invocation arguments straight through to the callback: void myCallback(int id, MyEvent const& event) { // Do something ... } void myMainLoop(bslma::Allocator *allocator = 0) { MyEventDispatcher schedA(bdlf::BindUtil::bind(&myCallback, _1, _2)); schedA.dispatch(10); MyEventScheduler schedB(bdlf::BindUtil::bindS(allocator, &myCallback, _1, _2)); schedB.run(10); }
void myCallbackWithUserArgs(int id, MyEvent const& event, int userArg1, double userArg2) { // Do something ... } void myMainLoop2(bslma::Allocator *allocator = 0) { MyEventDispatcher schedA(bdlf::BindUtil::bind(&myCallbackWithUserArgs, _1, _2, 360, 3.14)); schedA.dispatch(10); MyEventScheduler schedB(bdlf::BindUtil::bindS(allocator, &myCallbackWithUserArgs, _1, _2, 360, 3.14)); schedB.run(10); }
void myCallbackWithUserArgsReordered(int id, int userArg1, double userArg2, MyEvent const& event) { // Do something ... } void myMainLoop3(bslma::Allocator *allocator = 0) { MyEventDispatcher schedA(bdlf::BindUtil::bind( &myCallbackWithUserArgsReordered, _1, 360, 3.14, _2)); schedA.dispatch(10); MyEventScheduler schedB(bdlf::BindUtil::bindS(allocator, &myCallbackWithUserArgsReordered, _1, 360, 3.14, _2)); schedB.run(10); }
void myCallbackThatDiscardsResult(MyEvent const& event) { // Do something ... } void myMainLoop4(bslma::Allocator *allocator = 0) { MyEventDispatcher schedA(bdlf::BindUtil::bind( &myCallbackThatDiscardsResult, _2)); schedA.dispatch(10); MyEventScheduler schedB(bdlf::BindUtil::bindS(allocator, &myCallbackThatDiscardsResult, _2)); schedB.run(10); }
operator()
, but all the variations of the previous example could be given as well. struct MyCallbackObject { typedef void ResultType; void operator() (int id, MyEvent const& event) const { myCallback(id, event); } }; void myMainLoop5(bslma::Allocator *allocator = 0) { MyCallbackObject objA; MyEventDispatcher schedA(bdlf::BindUtil::bind(objA, _1, _2)); schedA.dispatch(10); MyCallbackObject objB; MyEventScheduler schedB(bdlf::BindUtil::bindS(allocator, objB, _1, _2)); schedB.run(10); }
MyCallbackObject
of the previous example, but illustrates that it can be passed by reference as well as by value: void myMainLoop6(bslma::Allocator *allocator = 0) { MyCallbackObject objA; MyEventScheduler schedA(bdlf::BindUtil::bind(&objA, _1, _2)); schedA.run(10); MyCallbackObject objB; MyEventScheduler schedB(bdlf::BindUtil::bindS(allocator, &objB, _1, _2)); schedB.run(10); }
struct MyStatefulObject { // DATA // ... public: void callback(int, MyEvent const& event) { // Do something that may modify the state info... } }; void myMainLoop7(bslma::Allocator *allocator = 0) { MyStatefulObject objA; MyEventScheduler schedA(bdlf::BindUtil::bind( &MyStatefulObject::callback, &objA, _1, _2)); schedA.run(10); MyStatefulObject objB; MyEventScheduler schedB(bdlf::BindUtil::bindS(allocator, &MyStatefulObject::callback, &objB, _1, _2)); schedB.run(10); }
bdlf::BindUtil
. Upon invocation, the invocation arguments are forwarded to the nested binder. MyEvent annotateEvent(int, MyEvent const& event) { // Do something to 'event' ... return event; } void myMainLoop8(bslma::Allocator *allocator = 0) { MyCallbackObject objA; MyEventScheduler schedA( bdlf::BindUtil::bind(&objA, _1, bdlf::BindUtil::bind(&annotateEvent, _1, _2))); schedA.run(10); MyCallbackObject objB; MyEventScheduler schedB( bdlf::BindUtil::bindS(allocator, &objB, _1, bdlf::BindUtil::bindS(allocator, &annotateEvent, _1, _2))); schedB.run(10); }
typename FUNC::ResultType
), the binder needs an explicit specification. This is done by using the bdlf::BindUtil::bindR
function template as exemplified below: typedef void GlobalResultType; struct MyCallbackObjectWithoutResultType { GlobalResultType operator() (int id, MyEvent const& event) const { myCallback(id, event); } }; void myMainLoop9(bslma::Allocator *allocator = 0) { MyCallbackObjectWithoutResultType objA; MyEventScheduler schedA(bdlf::BindUtil:: bindR<GlobalResultType>(objA, _1, _2)); schedA.run(10); MyCallbackObjectWithoutResultType objB; MyEventScheduler schedB(bdlf::BindUtil:: bindSR<GlobalResultType>(allocator, objB, _1, _2)); schedB.run(10); }
printf(const char*, ...)
. In the following code snippet, we show how the argument to the callBinder
function (redefined below for the reader's convenience) of section "Elementary construction and usage of <code>bdlf::Bind</code> objects" above can be bound to printf
: template <class BINDER> void callBinder(BINDER const& b) { b(10, 14); } void bindTest7(bslma::Allocator *allocator = 0) { const char *formatString = "Here it is: %d %d\n"; callBinder(bdlf::BindUtil::bindR<int>(&printf, formatString, _1, _2)); }
bindTest7
will create a binder, pass it to callBinder
which will invoke it with arguments 10
and 14
, and the output will be: Here it is: 10 14
#define BDLF_BIND_PARAMINDEX | ( | N | ) |
( \ (k_PARAM1 == N ? 1 : 0) + \ (k_PARAM2 == N ? 2 : 0) + \ (k_PARAM3 == N ? 3 : 0) + \ (k_PARAM4 == N ? 4 : 0) + \ (k_PARAM5 == N ? 5 : 0) + \ (k_PARAM6 == N ? 6 : 0) + \ (k_PARAM7 == N ? 7 : 0) + \ (k_PARAM8 == N ? 8 : 0) + \ (k_PARAM9 == N ? 9 : 0) + \ (k_PARAM10 == N ? 10 : 0) + \ (k_PARAM11 == N ? 11 : 0) + \ (k_PARAM12 == N ? 12 : 0) + \ (k_PARAM13 == N ? 13 : 0) + \ (k_PARAM14 == N ? 14 : 0))
#define BDLF_BIND_EVAL | ( | N | ) |
bdlf::Bind_Evaluator<typename bslmf::TypeListTypeOf<N,LIST>::Type, ARGS>\ ::eval(args, (list->d_a##N).value())
Referenced by bdlf::Bind_Invoker< RET, 1 >::invoke(), bdlf::Bind_Invoker< void, 1 >::invoke(), bdlf::Bind_Invoker< void, 5 >::invoke(), bdlf::Bind_Invoker< void, 4 >::invoke(), bdlf::Bind_Invoker< RET, 10 >::invoke(), bdlf::Bind_Invoker< void, 3 >::invoke(), bdlf::Bind_Invoker< RET, 7 >::invoke(), bdlf::Bind_Invoker< RET, 11 >::invoke(), bdlf::Bind_Invoker< void, 14 >::invoke(), bdlf::Bind_Invoker< void, 2 >::invoke(), bdlf::Bind_Invoker< RET, 6 >::invoke(), bdlf::Bind_Invoker< RET, 12 >::invoke(), bdlf::Bind_Invoker< void, 13 >::invoke(), bdlf::Bind_Invoker< RET, 9 >::invoke(), bdlf::Bind_Invoker< void, 9 >::invoke(), bdlf::Bind_Invoker< void, 12 >::invoke(), bdlf::Bind_Invoker< RET, 13 >::invoke(), bdlf::Bind_Invoker< RET, 8 >::invoke(), bdlf::Bind_Invoker< void, 8 >::invoke(), bdlf::Bind_Invoker< void, 11 >::invoke(), bdlf::Bind_Invoker< RET, 14 >::invoke(), bdlf::Bind_Invoker< void, 7 >::invoke(), bdlf::Bind_Invoker< void, 10 >::invoke(), bdlf::Bind_Invoker< RET, 3 >::invoke(), bdlf::Bind_Invoker< void, 6 >::invoke(), bdlf::Bind_Invoker< RET, 2 >::invoke(), bdlf::Bind_Invoker< RET, 5 >::invoke(), and bdlf::Bind_Invoker< RET, 4 >::invoke().