I'm trying to wrap my mind around the interaction of C++ lambda expressions and templates.
This code works as I expect:
#include <iostream>
int bar (int x, int (* f) (int))
{
return f (x);
}
double bar (double x, double (* f) (double))
{
return f (x);
}
int main ()
{
std::cout << bar (16, [] (int x) -> int { return x * x; }) << std::endl;
std::cout << bar (1.2, [] (double x) -> double { return x * x; }) << std::endl;
return 0;
}
So does this:
#include <iostream>
#include <functional>
int bar (int x, std::function<int (int)> f)
{
return f (x);
}
double bar (double x, std::function<double (double)> f)
{
return f (x);
}
int main ()
{
std::cout << bar (16, [] (int x) -> int { return x * x; }) << std::endl;
std::cout << bar (1.2, [] (double x) -> double { return x * x; }) << std::endl;
return 0;
}
So far, so good. But neither of the following examples compile:
#include <iostream>
template <typename T>
T bar (T x, T (* f) (T))
{
return f (x);
}
int main ()
{
std::cout << bar (16, [] (int x) -> int { return x * x; }) << std::endl;
std::cout << bar (1.2, [] (double x) -> double { return x * x; }) << std::endl;
return 0;
}
and
#include <iostream>
#include <functional>
template <typename T>
T bar (T x, std::function <T (T)> f)
{
return f (x);
}
int main ()
{
std::cout << bar (16, [] (int x) -> int { return x * x; }) << std::endl;
std::cout << bar (1.2, [] (double x) -> double { return x * x; }) << std::endl;
return 0;
}
GCC version 8.3.0 (with -std=c++17) gives an error message:
no matching function for call to 'bar(int, main()::<lambda(int)>' (and
another for the "double" version) and "template argument
deduction/substitution failed: main()::<lambda(int)> is not derived
from std::function<T(T)>" (for the second failing example).
However, this assignment works:
std::function<int (int)> f = [] (int x) -> int { return x * x; };
Could anyone please shed light for me? (Obviously, this isn't practical code. This is just an attempt to learn.)
The problem is that implicit conversion (from lambda to function pointer or std::function
) won't be considered in template argument deduction; then in your 3rd and 4th sample type deduction for T
on the 2nd function argument (i.e. the lambda) fails.
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.
In this case T
could be deduced from only the 1st function argument; you can declare the 2nd one with non-deduced context, to exclude it from deduction.
e.g.
template <typename T>
T bar (T x, std::type_identity_t<T (*) (T)> f)
{
return f (x);
}
template <typename T>
T bar (T x, std::type_identity_t<std::function <T (T)>> f)
{
return f (x);
}
PS: std::type_identity
is available from C++20, before that you can make one by your own, it's not difficult.
LIVE
Your failing code is expecting function pointers during template argument deduction and lambdas are not function pointers.
However, non-capturing lambdas such as yours can be converted to function pointers.
The most terse way to do this is to apply a unary +
. This works because a +
is valid for pointers, triggering a conversion before template substitution occurs.
#include <iostream>
template <typename T>
T bar (T x, T (* f) (T))
{
return f (x);
}
int main ()
{
std::cout << bar (16, +[] (int x) -> int { return x * x; }) << std::endl;
// ^ this is the only change
std::cout << bar (1.2, +[] (double x) -> double { return x * x; }) << std::endl;
// ^ this is the only change
return 0;
}
See it compile on godbolt
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With