c++ metacall()()()™

What is happening here ?


Compiler dispatches all the calls from the main() examples, above to the particular “metacall” instance,  Each of them calls, is passed to the current processor, passed as template argument.  ::dbj::meta_call::default_processor is just the default one of the (potentially) many processors functors available.

Remember the first argument. It’s type decides what will be done with a paraticular  call, in the stream of calls.

This is how default processor behaves. It simply calls the first argument with the remaining arguments , if it is found to be invocable, that is. This is how the bridge between metacall mechanism and user defined types is established.

By default or by using user defined processor.

// since user::processor inherits the default processor
// this just expands on its behaviour
// to deal with bool-eans
cs::call_streamer< user::processor > cst;
// all cought and processed by
// the user::processor
cst(true,1,2,3)(false,4,5,6) ;

If it is not callable and there is no processor for a type of the first argument arrived into it, that call is by default simply logged.  The safe assumption is here, the user has already delivered a processor for dealing with that type. For an example see the user defind proceesor bellow which deals with booleans.

The term “bridge” sometimes used, by me, in this context means an “bridge” to the implementation. I am also using the term “bridge” because this implementation hiding concept, reminds me (somewhat) on the “bridge pattern” as described by Gof4.

Actual class diagram of metacall and the user processor (in this online project) is this:

metacall + user

Which processor/bridge functor is going to be used is decided by the users at compile time. It’s type is a template argument. Let us repeat the usage of this composition mechanism, from the main() above:

namespace cs = dbj::metacall;
// default metacall available
cs::default_cs	cst ;

Here, user whose code is in the namespace user , has also provided her extension of the default processor. Here is its interface (yes, the actual code is in that project on line)

namespace user
namespace cs = dbj::metacall;
user defined processor 
user does not need to inherit from the default processor
but it can ... in this case to take care of 
bools sent as the first arg

dbj::meta_call::call_streamer< user::processor > ucs;

ucs(true,1,2,3)("oopsy!",4,5,6) ;
struct processor : cs::default_processor {
use the operators from the base
using cs::default_processor::operator();
here we take care of bool literals too
thus we expand the functionality of the default processor
template <typename ... Args>
void operator () (bool bool_arg_, Args ... args_) const 
/* implementation here */
} ;
} // namespace user

Thus in the main() where user has decided to use her own processor:

// since user::processor inherits the default processor
// this just expands on its behaviour
cs::call_streamer< user::processor > cst;

And now cst is completely interchangeable with the default metacall with the added benefit of user defined bool calls processing.

// this two calls will be processed by the
// user define processor
cst(true,1,2,3)(false, 4,5,6) 
// but this call will finish inside the default processor
// which will call the user pre-defined function add()

What we have till now is enough to use this modern c++ call-stream implementation. And enjoy its default behaviour.