This is an update on my previous post. With a moral to the story too. I might hope some C++ beginners might learn something useful 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
1 2 |
// 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()
that example looks fairly generic and simple to me.
Ok then. A quick copy and paste into my Visual Studio.
A few variations later I indeed concluded template arguments add_generic()
cannot be deduced. So let me try with generic lambda.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/* Improved codes 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 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 a 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 with its arguments.
This is a template (function) :
1 2 3 4 5 |
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 everything, it has a type. It exists.
1 2 3 4 5 |
// 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 the template function instance (aka definition) is the function one can use by calling it. That object is Callable, and that is what std::apply
needs as the first argument. The first argument can not be a template name, it has to be a template definition if the callable object is a template.
1 2 3 4 5 6 7 8 9 10 11 |
// proper use of std::apply // is with function // template definitions 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 types to the adder and thus removes all the magic from the code using the generic lambdas.
1 2 3 4 5 6 7 8 |
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. The lambda-based solution will be unchanged
1 2 3 4 5 6 7 |
// 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:
1 2 3 4 5 6 7 |
// proper use of std::apply // but for any types const auto one = std::apply( template_adder< decltype(a) >, std::make_pair (a , b ) ); |
Hmm… even less magic here.
I think I might suggest, just using generic lambda and leaving 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.