c++ metacall()()()™


A paradigm shift (or revolutionary science) is, according to Thomas Kuhn, in his influential book The Structure of Scientific Revolutions (1962), a change in the basic assumptions, or paradigms, within the ruling theory of science. It is in contrast to his idea of normal science.

metacall series


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_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.

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"
	// 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”.

metacall core mechanism

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.

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

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 */
} ;