
metacall series
- Part One: metacall foundations, JavaScript format
- Part Two: metacall C# Binding
metacall()()()™
metacall broadly means: “call of calls”
Part 3 : C++ Binding
It is not attached to any programing language, yet metacall as a concept is born (and bred) in the domain of type-less JavaScript. Slight difficulty is that good old/new/standard C++, is v.s. JavaScript, one strongly typed language. In English: types are everything.
Also, C++ is compiled language, vs dynamic (interpreted) JavaScript. Although, metaclass is already implemented in C#. And conceptualy, as far as types are concerned, C# is somewhere in between, (but not in the middle !) even in it’s latest reincarnation.
Ditto, I think metacall must be implemented in C++ too. Let’s jump into the deep.
metacall is not an implementation idiom of a single programming language. It is an (application architecture) concept. And concept is not very good if it is not universally applicable. In which case it is not ubiquitous, and therefore not used everywhere by everyone. Not successful, in one word.
In case of C++, I have decided to make an (sort of a) type-less metacall type. Which in C++ is achieved with generic programming, aka “templates”.
Thus metacall C++, is an C++ interfacing mechanism which allows for more or less same call-streaming as in JavaScript. Where one can “call” anything with “any” arguments.
call_stream(first_1, a,b,c) // call 1
(first_2,1,2,true) // call 2
(first_3, 3.14, "literal") ; // call 3
This is one “fluent” API indeed. And by default it is very forgiving. metacall can be logical instance of any kind-of-a metacall type. It just has to confirm to certain interface, which we shall see very soon. What matters here is this paradigm of “endless” stream of calls.
We shall follow here the “comand and arguments” paradigm. First argument of each call, by default defines the behaviour. If it is callable it will be simply called with the rest of the argument’s given, for one call. The “normal” equivalent of the above is:
first_1(a,b,c) ;
first_2(1,2,true);
first_3(3.14, "literal");
Why would anybody use this?
Why not just using (for example) variable number of arguments mechanism ? I think it is simple:
metacall enables quick building of an fluent API.
metacall is giving place of control of the mechanism behind the API.
metacall quickly and succesfully hides the complexity. It is also an “facade pattern”.
Importan benefit here is dealing with legacy code. Three calls above are just any three old functions, but as part of metacall sequence suddenly they have a new meaning. The belong to the same API, same logic in the back and are very likely, operating on the same data in the back. All very nicely encapsulated and decoupled from the callers.
Here is the usage of the c++17 implementation, I will show soon.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
int main() { namespace cs = dbj::meta_call; cs::default_cs cst ; // following call // is dispatched to an processor for handling string literals cst("add", 1, 2) // pre-existing function is immediately callable from a "metacall" (user::add,4,5,6) // if found this is dispatched to specialized processor for handling char ('X', 1, 2, 3) // if found this is dispatched to specialized processor for handling int list (7, 6, 8, 9) // if found this is dispatched to specialized processor for handling bool (true, 11) // and we can guess by now where this will go (&std::cout, "this", "goes", "to", "generic", "processor"); // make a command to deal with single int's user::generic_command<int> my_cmd("LABEL"); // just proceed woth the same cassl stream instance cst(my_cmd, 9)(my_cmd, 6); } |
Above is the the real code that compiles and runs. (GitHub repository here). For each call in the stream, frist argument is significant. It is a “command”.

The “metacall” default implementation allows calls where first argument and any other argument can be “anything”; a string literal, function, intrinsic type or object (aka class instance). With any number of any type of arguments . Of course “anything” that has to compile at the moment of usage in the C++ source.
Let’s jump straight to the default call stream
code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/* front end of the c++ "metacall" mechanism delivers the "calling experience" to the clients */ template<typename PROC> struct call_streamer final { PROC proc_{}; template < typename CMD_, typename... Args> const call_streamer& operator()(CMD_ cmd_, Args... args_) const { proc_(cmd_, args_...); return *this; } }; |
Perhaps surprisingly simple. So, in this solution there is one dbj::meta_call::call_streamer<PROC>
class template, serving the needs of all.
Note: yes, I am aware of locking issue. In ISO C++ that is easy to solve, but it is not shown here, so we can focus on the core subject. But why are there two classes?
metacall and processor are split into two, so that processor can be changed.
The metacall (functor) type makes the “meta calling” possible. It is also passing each call to the “processor” type in the back.
The “trick” is in the (generic) function template call operator. Where the operator ()
, has a simple task of passing the first argument and variable number of arguments sent, to the proc_() (aka “processor”) function. Where the real action happens. In the application space, that I am calling “behind”.
And that function is actually another functor. In the code (simplified, full code is on GitHub) it’s interface is this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/* Default metacall processor each call can be a 'command' + variable number of arguments but does not have to be */ struct default_processor { /* user defined processors must be functors implementing this method */ template <typename T, typename... Args> void operator () (T const& cmd_, Args... args_) const { /* implementatin here */ } } ; |
[nextpage title=”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.
1 2 3 4 5 6 7 |
// 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:

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:
1 2 3 |
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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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:
1 2 3 |
// 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.
1 2 3 4 5 6 |
// 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() (user::add,42,42); |
What we have till now is enough to use this modern c++ call-stream implementation. And enjoy its default behaviour.
[nextpage title=”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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
template<typename PROC> class call_streamer_async final { PROC proc_; public: 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:
1 2 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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:
1 2 3 4 5 6 7 8 9 10 11 |
/* 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
1 2 |
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:
1 2 3 4 5 6 7 |
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.
1 2 3 4 5 |
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”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* 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.
1 2 3 |
/* 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
.
1 2 3 4 5 6 7 8 9 10 11 |
// 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::mul<int>,4,6) (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.
Conclusion
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.