c++ metacall()()()™

How is that possible in C++?

The “trick” is in the generic function call operator. As implemented inside this non-generic class. Actually (at the moment) two templated “operator ()” overloads, and one specialization1, appear to be sufficient. Where each opearator (), has a simple task to pass the first argument and va_list pointer to the dbj::bridge() function. Where the real processing happens. The application space, that I am calling “behind”.

So, what is happening here? Compiler dispatches all the calls from the main() example above to the CallStream instance, one of these three function call operators. Each of them calls one of the (potentially) many bridge functions available. The “bridge” in this context means the same as in the CallStream concept: a “bridge” to the implementation. I am also using the term “bridge” because this implementation hiding concept, reminds me (somewhat) of the “bridge pattern” as described by Gof4. Which bridge function is going to be called is decided by the c++ compiler, following the usual c++ rules of function overloading. A real “c++ kabala” when we mix in overloaded, with generic with specialized templated “bridges”.

Here is the generic bridge function and a handful of basic specializations of it.

What we have till now is enough to call-stream in c++. All calls where the first argument is not int, bool, char, or string literal will be passed to the generic dbj::bridge.

 

The last call above is passing the c++ iostreams library std::cout object as the first parameter to the call. Compiler realizes there is no bridge specialized for handling this type (std::ostream), and thus uses the generic bridge.

Detour: Better design?

One could imagine an “OO solution” based on inheritance, where only one generic bridge is used which somehow “cleverly” dispatches calls to their final destinations. That would inevitably require looking into the concrete type of the first call parameter, thus quite slow and cumbersome. Also a solution is not very resilient to change. It will be in essence one huge switch to be frequently updated as new types have to be handled by the CallStream. And of course, this single generic bridge+dispatcher will be very difficult to maintain and re-use as a library solution. It is much better to use a c++ compiler to find your specialized bridge, that you have “built” (inside your own code) so that CallStream can pass the appropriate calls to your particular implementation you want to place “behind” a CallStream mechanism.

This is all solvable as above with templates and specializations.  For a “classical OO” solution, consider this :

We can easily imagine a solution based on a hierarchy of concrete CallStreams. Which will generate much more c++ code, and again a solution which will require inheriting from the core mechanism for each new type to be handled. A classical c++ nightmare where there is a huge logical hierarchy, physically scattered over many applications, with a root of it being in some single library. The root which therefore has to be “Cast in blood and signed in stone” is never ever to be changed. Also above there is no implementation, only required behavior. Solutions as the one above do generate redundant implementations “all over the place”. I am glad to notice that once more inheritance is rendered as not good, for a solution to be used in real-life situations.

%d bloggers like this: