c++ metacall()()()™

Let's clarify


Not to worry. Behavioural patterns are very flexible and difficult to grasp at first.  Here is a (uml sequence)  diagram showing metacall and processor dancing together.

Two calls: one to the exsiting function void add(int,int); and the other that provokes “not found and log”, iniside the processor.

There is no much more to it. That is the all of the functionality one needs to start metacalling, using whatever one wishes to use behind, that existed before. Any other pre-existing function for example, all clustered together behind one wonder API, almost instantly.

Asynchronous metacall

Yes that is delivered too. For those use cases when order of the execution of single calls in the metacall stream, is not important

template<typename PROC>
class call_streamer_async final
 PROC proc_;
 template < typename CMD_, typename... Args>
 const call_streamer_async& operator()(CMD_ cmd_, Args... args_) const
 [[maybe_unused]]auto must_not_discard_ = std::async(
	proc_, cmd_, args_...
	return *this;
// the default ASYNC metacall definition
using default_mca = call_streamer_async< default_processor >;

Beside being asynchronous this and previous metacall streamer behave exactly the same. This one will finish the overal metacall faster and will contribute to the faster program hosting it. But remember: call will be executed out of sync.

Comand + aguments = API

This mechanism is indeed not based on an hierarchy of implementations (aka processors). Inheritance is optional, not mandatory.

Example shown above:  user::processor, indeed inherits from the dbj::meta_call::default_processor, but it does not have to.  Due to the concept of the dbj::meta_call::call_streamer<PROC> , one can use any type that is INVOCABLE  to act as a processor as long as invoking  it, conforms to the following signature:

template <typename T, typename... Args>
void operator () (T const& cmd_, Args... args_) const ;

That user made processor will be then able to simply replace the default one and will receive every call “streamed in”. In case of using functors as processor, the added benefit is we can easily compose the workable drop-in-replacement solution based on overloading the above operator

struct my_processor  
to be "universal" user defined processors should
be implementing this "catch all" method
template <typename T, typename... Args>
void operator () (T const& cmd_, Args... args_) const ;
User also can add overloads of the above for whatever reason
For example to be able to stream in calls to her
MYSQL proxy
template < typename... Args>
void operator () ( MYSQL const & cmd_, Args... args_) const ;
} ;

Sometimes Difficulty is in the Flexibility

All streamer/processor combinations work and shape up a behaviour close or very far from the default one.  A lot of people have difficulty seeing the benefit of this.  For example user (also a good pupil reading this) might decide to use metacall as an API for the data base proxy front end. And nothing else.

In that case the user processor simply becomes:

/* MYSQL is base functor of commands for example */
struct MYSQL {} ;

struct my_sql_proxy_calls_processor  
just he call do the with MYSQL proxy
template < typename... Args>
void operator () ( MYSQL const & cmd_, Args... args_) const ;
} ;

And then this user can pre-package the metacall to act as a front end to the MYSQL proxy

using sqlproxy_call_stream = 
   dbj::meta_call::call_streamer< my_sql_proxy_calls_processor > ;

Any calls streamed into this will have to start with MQSQL type or anything inherting from it:

sqlproxy_call_stream sql ;

// imaginary namespace 
using namespace user_sql_proxy ; 

sql( open, "localhost:1234@admin@example.xyz" )(select, "*", "people");
// and so on

metacall mechanism has no return values.

Perhaps you have noticed that ‘feature’ aleady. Interesting issue or attribute of this mechanism, is the user/developer has to think of the state more clearly.

There is no return values from metacall calls. You either provide accumulator for the return values, or you design some global singletons to keep your data (DOM is one real life example of that concept). Very interesting.

outcome & outcome_ref = outcome{};
result_set & result_set_ref = result_set{};

sql( outcome_ref, open, "localhost:1234@admin@example.xyz" )
(result_set_ref, select, "*", "people"); // and so on

outcome and result_set here, can also be globals. It depends on the users application design.

API as the obvious use-case

Now, back to our shiny solution based on default call stream mechanism and generic processor ( aka bridge ), possibly specialized by “customers” for their specific purposes.

What would be the most likely use case?

This time I will pay attention to the customers who are implementing some library in the (right) spirit of c++ std : a lot of generic functions and the whole solution in header files. Also, they want to have an “metacall enabled API” interfacing to their library.

Most likely this customer wants to hide her complex “things” behind a dbj::meta_call, and thus make the whole solution API, nice and easy and eminently usable. Accidentally the same customer is keen on “cmd & arguments paradigm”, perhaps wishing to deliver her existing lib into an API good for  command line interface (CLI) apps implementation,

How would that customer quickly implement the  call stream concept and  a small layer of code necessary  to make API?

Let’s keep this sample, simple but still valid c++. Let us call the customers lib “ops”, short for “operators”.

the whole 'ops' library is made out of inline
generic functions and no specializations 
namespace ops {
/* generic functions to add or multiply "anything" 
that has binary '+' and '*' operators 
template<typename T> inline T 
   add( const T & a, const T & b) { 
        return a + b ; 
template<typename T> inline T 
   mul( const T & a, const T & b) { 
       return a * b ; 
} // ops ns

This is the whole library. And here is the code required so that meta-call-stream-ed API for users can be delivered.

EMPTY -- no code

Yes,  that is right.  Just use the default metacall with the “ops” lib availabe and you will have instant call-stream-ed API.

// default metacall out-of-the-box
// is fully functional
int main(  )
namespace cs = dbj::meta_call;
cs::default_cs	cst ;
     cs (ops::add<int>,3,5)
        (ops::add<float>,2.3,7.12) ; 
return 0;

Also, just to confirm once more, the metacall malleability, if one passes some other non ‘ops’ operations here, or anything else, this code will still work.

Of course the metacall instance above is valid for passing to it any calls. It is the same as ever before. It will find any specialized call operator presented here, to pass the calls to their intended implementations.


All this C++ “snazzy” idioms, are not here because of my “requirements” or dictate. I am showing all these to confirm the eligibility of the whole metacall concept for the C++ programming too.

This is one very flexible but simple mechanism. Your imagination is required.