C++ std::apply made usable

So little C++ so much good!

Intro

Contents

std::apply is used to Invoke a Callable object with a tuple of arguments.

Somehow that statement left me wanting for more. That is one very corralling definition.

std::apply is a typical example of those numerous standard C++ std lib types begging to be made comfortable and usable.  Not just narrowly usable.  I can (you can, we can) see a bit wider scope for std::apply  use cases. Let’s try.

Certainly, one can try and go ahead and just use it. But.  In its raw form, std::apply requires a lot of typing. That code becomes clunky, unreadable and complex.

So, I made one little wrapper that is really simple but makes std::apply usage, much more palatable.

The API

Design principle in here is: you first make specific “applicator” type, from a specific Callable object. Then you use it, wherever you need it.

// summa is a function returning the sum of values of its arguments
// make applicator type from our summa function
auto sumator = dbj::make_applicator(summa);

Thus summator is a proxy to the services of the summa function. summator  now has the methods encapsulating the required interaction of std::apply and the summa function. Thus it delivers simpler and easier to use interface v.s the naked interaction of std::apply and the Callable it is using.

Please find the suma in the Appendix below.

Let us now see before and after use cases. To each example below, I have added std::apply raw usage, on the line below. So that you can compare and decide what works best for you.

// Test Unit is defined as #define TU(x) dbj::log(x,#x)
// it prints the result and the expression that made it

// after: std::pair as arg
TU(sumator(std::pair(1, 2)));
// before: naked usage
TU(std::apply(summa, std::pair(1, 2)));

// std::array as expected too
std::array<int, 5> a5 = { 1, 2, 3, 4, 5 };
// after
TU(sumator(a5));
// before 
TU(std::apply(summa, a5));

Not a big deal. Shorter but just slightly shorter. Ok, how about using tuples, as primary and only argument type to be passed (by design ) to std::apply. Here is the difference.

// passing tuple of args
// after
// NOTE: because of the summa() just one type please
TU(sumator(2.0, 3.0, 1.2, 2.3));

// before, the std way
TU(std::apply(summa, std::make_tuple(2.0, 3.0, 1.2, 2.3)));

At least for me, that’s a big difference in ease of usage. And then there are types that my API can do, and std::apply can not do.

// init list arg
TU(sumator({ 1,2,3 }));
// no can do -- TU(std::apply(summa, {1,2,3}));

Initializer list’s, std::apply simply does not let you use. And what about native arrays? Yes, I can do too. std::apply can not be called with native array as an argument.

// native array arg
int a5[]{ 1, 2, 3, 4, 5 };
TU(sumator(a5));
// no can do -- TU(std::apply(summa, a5));

I know std::array is rather nice, and I use it too whenever I can, but in real-life code, native arrays no one can avoid. There is a legacy ocean of them native arrays as arguments.  Instead of writing your own transformations from std array to native array, feel free to use my API.

Stunts?

Ok, I do not advise stunts, but only this time.  What about this “funky” situation:

F( a, b,  F(c, d))

Function result used as one of the arguments to the same function. How do we do that:

// sumator as arg
// it is as simple as that
TU(sumator(2.0,3.0, sumator(11.2,12.3) ));

// how aobut naked std::apply?
// doable but not nice at all
// close your eyes :)
TU(std::apply(summa, std::make_tuple(2.0, 3.0,
  std::apply(summa, std::make_pair(11.2, 12.3))
)));

As long as you understand the purpose of std::apply, I am pretty sure you will find good use for this little utility.  In that case, just use it. Feel free to experiment.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.