Never underestimate Generic Lambda in Modern C++

Generic Lambda

This is an update of my previous post. With a moral to the story too.  I might hope some C++ beginner might learn something useful here too.

[On-line source code is here]

 

Having my usual morning workout in the brain gym called cppreference.com, I noticed a comment inside the example snippet on the page  about std::apply

// template argument deduction/substitution fails
std::cout << std::apply(add_generic, std::make_tuple(2.0f,3.0f)) << '\n';

Hmm, says I: why is this? add_generic() in that example looks fairly generic and simple to me.

Ok then. A quick copy and paste into my trusty Visual Studio.

Few variations later I indeed concluded template arguments of the add_generic() cannot be deduced.  So let me try with generic lambda.

/*
Improved code sample taken from:
http://en.cppreference.com/w/cpp/utility/apply
*/

#include <iostream>
#include <tuple>

/*
template solution does not work ? 

template<typename T>
T adder(const T & first, const T & second)
{
    return first + second;    
}
*/
// generic lambda solution works
auto adder = [](auto first, auto second)
{
  return first + second; 
};
 
int main()
{
    std::cout << std::apply(adder, std::make_pair(1,2)) << '\n';
    std::cout << std::apply(adder, std::make_tuple(2.0f,3.0f)) << '\n'; 
}

And it worketh. Nice and simple, generic modern standard C++ code.

Why is a generic lambda working where generic template function does not? Well, there is an explanation and it is rather obvious. Which makes it difficult to spot immediately.

The C++ template is just that: a template. An empty vessel waiting to be instantiated into a type, using template arguments.

This is a template (function) :

template<typename T>
T template_adder (T first, T second)
{
   return first + second;
}

Thus ‘template_adder’ is as close to nothing as you can get… it is just a template name.  Its instance, on the other hand, is C++ everything, it has a type. It exists.

// template function instance 
template_adder<int>

// template function instance 
template_adder<float>

Above are two types made to exist by instantiating a template.  For a compiler, a template is “nothing”, just a declaration, and its instance, or definition, is “everything”.  Unused templates are simply removed by a compiler.

So, the std::apply requires a callable object as its first argument. And the template is not any kind of object, and template function instance (aka definition) is the function one can use by calling it.

That object is Callable type, and that is what std::apply needs as the first argument. The first argument can not be a template declaration, it has to be a template definition, if the callable object is a template too.

// proper use of std::apply 
// is with function template definitions
// using the definition one
const auto one = 
   std::apply(
      template_adder<int>,   std::make_pair (1   , 2   )
   );
const auto two = 
   std::apply(
       template_adder<float>, std::make_tuple(2.0f, 3.0f)
   );

That works but requires providing the concrete types to the adder and thus removes all the magic from the code using the generic lambdas. Like here:

const auto v1 = 
   std::apply(
        adder, std::make_pair(1, 2)
   );
const auto v2 = 
   std::apply(
        adder, std::make_tuple(2.0f, 3.0f)
   );

Now consider using this in a situation where the two types to be added are not obvious fundamental types. Lambda-based solution will be unchanged

// a and b 
// can be "anyting"
// not just trivial types
const auto v1 = 
   std::apply(
        adder, std::make_pair(a, b)
   );

as long as there is ‘+’ operator that can ‘add’ the a and b above the lambda based solution is unchanged.

Now how would one do this for the template function which needs to be a specific definition required to add two arbitrary values of two arbitrary types? Perhaps something like:

// proper use of std::apply 
// but for any types
const auto one = 
   std::apply(
      template_adder< decltype(a) >, 
      std::make_pair (a   , b   )
   );

Hm … even less magic here.

I think I might suggest, just use generic lambda and leave it to your shiny modern C++ compiler to sort it out and generate the right kind of code.

Meanwhile. Back at  cppreference.com HQ, and not long after, my improvement was adopted and the example code changed accordingly.  It is nice to communicate with nice people behind ccpreference.com.

That was one good C+++ morning indeed.

 

 

So little C++ so much good!
So little C++ so much good!