The requirement is to be able to handle two-parameter packs in the same time. Just to be sure we are on the same page, using one parameter pack call, on a single c++ function, looks like this:
1 2 3 4 5 6 7 8 |
// variadic function declaration template < typename ... Args > void function ( Args ... args_ ); // calling with variable number of arguments // of variable types function( "string", true, 2.3f ); // second call function( "anothe string", PI, now() ); |
A number of arguments on the same variadic function are unspecified and arguments can be of various unrelated types. Good old printf()
is the most famous example, in existence from the primordial days of C.
1 2 3 |
// first argument is format string // second is variable number of arguments int printf( const char * , ... ) ; |
A bit easier to declare and code in standard C++ but nothing new.
So, how do we implement this requirement? Two param packs in the same time? Many have tried, There are few complex class-based solutions.
The Concept
Let us imagine we have to merge two ad-hoc param pack sets. The legacy way would probably be to encapsulate the solution in a class and use it like this:
1 2 3 4 |
// constructor receives the first param pack. list_merger<int> marge_two_( 1,2,3 ) ; // first list // list to be assigned as the second p.pack auto merged_sorted = merge_two_.assign( 45 , 22, 13, 88 ); |
Above is not lambda calculus a.k.a. functional programming. And. It is not very elegant.
Lambda based solution in use looks like this:
1 2 |
// auto merged_sorted = merge_two_(1,2,3)( 45 , 22, 13, 88 ); |
And, of course, under different requirements, this same concept, will also serve the purpose.
1 2 3 4 |
// two param packs // driving some processing in the back auto rezult = process("command", "arg1", "arg2") ("subcomand", "option1" , "option2"); |
Looks suspiciously like “metacall“. This is because it is. But simpler. It’s only purpose is to receive two param packs “in one go”. And do something with two of them. And optionally return something.
Lambda, the C++ core language feature, allows for simple, elegant and effective solutions. This is one of them.
The Solution
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// (c) 2020 by dbj@dbj.org // Licence CC BY SA 4.0 // two param packs for one function // template <typename ... Args > auto two_ppacks(Args ... ppack_1 ) { // return the lambda to be called // to actually do the job // after it takes the second param pack return [=](auto ... ppack_2 ) { // make two tuple sets from two param packs auto args_set_1 = make_tuple( ppack_1 ... ); auto args_set_2 = make_tuple( ppack_2 ... ); // in this instance just print them cout << endl << boolalpha << args_set_1 ; cout << endl << boolalpha << args_set_2 << endl; }; }; |
For tuple printing, I use the variant of this.
Same as ever I tend to avoid, what I am calling “pathologically generic” solution. Thus preserving the simplicity lambda is giving me.
1 2 |
// the call example two_ppacks(1, false, 'X')(6.32f, "Hola!", 3.14156); |
The return type is obviously completely arbitrary. The point is in the feasibility of this solution when compared to the usual ones.
And simplicity is resilience. And resilience is feasibility.