Pass lambda as template function parameter - c++

Why doesn't the following code compile (in C++11 mode)?
#include <vector>
template<typename From, typename To>
void qux(const std::vector<From>&, To (&)(const From&)) { }
struct T { };
void foo(const std::vector<T>& ts) {
qux(ts, [](const T&) { return 42; });
}
The error message is:
prog.cc:9:5: error: no matching function for call to 'qux'
qux(ts, [](const T&) { return 42; });
^~~
prog.cc:4:6: note: candidate template ignored: could not match 'To (const From &)' against '(lambda at prog.cc:9:13)'
void qux(const std::vector<From>&, To (&)(const From&)) { }
^
But it doesn't explain why it couldn't match the parameter.
If I make qux a non-template function, replacing From with T and To with int, it compiles.

A lambda function isn't a normal function. Each lambda has its own type that is not To (&)(const From&) in any case.
A non capturing lambda can decay to To (*)(const From&) in your case using:
qux(ts, +[](const T&) { return 42; });
As noted in the comments, the best you can do to get it out from a lambda is this:
#include <vector>
template<typename From, typename To>
void qux(const std::vector<From>&, To (&)(const From&)) { }
struct T { };
void foo(const std::vector<T>& ts) {
qux(ts, *+[](const T&) { return 42; });
}
int main() {}
Note: I assumed that deducing return type and types of the arguments is mandatory for the real problem. Otherwise you can easily deduce the whole lambda as a generic callable object and use it directly, no need to decay anything.

If you don't need to use the deduced To type, you can just deduce the type of the whole parameter:
template<typename From, typename F>
void qux(const std::vector<From>&, const F&) { }

Correct me if I am wrong, but template parameters deduction deduces only exact types without considering possible conversions.
As a result the compiler cannot deduce To and From for To (&)(const From&) because qux expects a reference to function, but you provide a lambda which has its own type.

You have left absolutely no chance to compiler to guess what is To. Thus, you need to specify it explicitly.
Also, lambda here needs to be passed by pointer.
Finally, this version compiles ok:
template<typename From, typename To>
void qux(const std::vector<From>&, To (*)(const From&)) { }
struct T { };
void foo(const std::vector<T>& ts) {
qux<T,int>(ts,[](const T&) { return 42; });
}

You're expecting both implicit type conversions (from unnamed function object type to function reference type) and template type deduction to happen. However, you can't have both, as you need to know the target type to find the suitable conversion sequence.

But it doesn't explain why it couldn't match the parameter.
Template deduction tries to match the types exactly. If the types cannot be deduced, deduction fails. Conversions are never considered.
In this expression:
qux(ts, [](const T&) { return 42; });
The type of the lambda expression is some unique, unnamed type. Whatever that type is, it is definitely not To(const From&) - so deduction fails.
If I make qux a non-template function, replacing From with T and To with int, it compiles.
That is not true. However, if the argument was a pointer to function rather than a reference to function, then it would be. This is because a lambda with no capture is implicitly convertible to the equivalent function pointer type. This conversion is allowed outside of the context of deduction.
template <class From, class To>
void func_tmpl(From(*)(To) ) { }
void func_normal(int(*)(int ) ) { }
func_tmpl([](int i){return i; }); // error
func_tmpl(+[](int i){return i; }); // ok, we force the conversion ourselves,
// the type of this expression can be deduced
func_normal([](int i){return i; }); // ok, implicit conversion
This is the same reason why this fails:
template <class T> void foo(std::function<T()> );
foo([]{ return 42; }); // error, this lambda is NOT a function<T()>
But this succeeds:
void bar(std::function<int()> );
bar([]{ return 42; }); // ok, this lambda is convertible to function<int()>
The preferred approach would be to deduce the type of the callable and pick out the result using std::result_of:
template <class From,
class F&&,
class To = std::result_of_t<F&&(From const&)>>
void qux(std::vector<From> const&, F&& );
Now you can pass your lambda, or function, or function object just fine.

Related

Passing a lambda function to a template method

I have the following templated method:
auto clusters = std::vector<std::pair<std::vector<long>, math::Vector3f>>
template<class T>
void eraserFunction(std::vector<T>& array, std::function<int(const T&, const T&)> func)
{
}
And I have a function that looks like
auto comp1 = [&](
const std::pair<std::vector<long>, math::Vector3f>& n1,
const std::pair<std::vector<long>, math::Vector3f>& n2
) -> int {
return 0;
};
math::eraserFunction(clusters, comp1);
However, I get a syntax error saying:
116 | void eraserFunction(std::vector<T>& array, std::function<int(const T&, const T&)> func)
| ^~~~~~~~~~~~~~
core.hpp:116:6: note: template argument deduction/substitution failed:
geom.cpp:593:23: note: 'math::method(const at::Tensor&, const at::Tensor&, int, float, int, int, float)::<lambda(const std::pair<std::vector<long int>, Eigen::Matrix<float, 3, 1> >&, const std::pair<std::vector<long int>, Eigen::Matrix<float, 3, 1> >&)>' is not derived from 'std::function<int(const T&, const T&)>'
593 | math::eraserFunction(clusters, comp1);
The function call tries to deduce T from both the first and second function parameter.
It will correctly deduce T from the first parameter, but fail to deduce it from the second parameter, because the second function argument is a lambda type, not a std::function type.
If deduction isn't possible from all parameters that are deduced context, deduction fails.
You don't really need deduction from the second parameter/argument here, since T should be fully determined by the first argument. So you can make the second parameter a non-deduced context, for example by using std::type_identity:
void eraserFunction(std::vector<T>& array, std::type_identity_t<std::function<int(const T&, const T&)>> func)
This requires C++20, but can be implemented easily in user code as well if you are limited to C++11:
template<typename T>
struct type_identity { using type = T; };
and then
void eraserFunction(std::vector<T>& array, typename type_identity<std::function<int(const T&, const T&)>>::type func)
std::identity_type_t<T> is a type alias for std::identity_type<T>::type. Everything left to the scope resolution operator :: is a non-deduced context, which is why that works.
If you don't have any particular reason to use std::function here, you can also just take any callable type as second template argument:
template<class T, class F>
void eraserFunction(std::vector<T>& array, F func)
This can be called with a lambda, function pointer, std::function, etc. as argument. If the argument is not callable with the expected types, it will cause an error on instantiation of the function body containing the call. You can use SFINAE or since C++20 a type constraint to enforce this already at overload resolution time.

std::invoke - perfect forwarding functor

Trying to understand why the following example fails to compile:
#include <functional>
template <typename F>
void f1(F&& f)
{
std::forward<F>(f)("hi");
}
template <typename F>
void f2(F&& f)
{
std::invoke(f, "hi"); // works but can't perfect forward functor
}
template <typename F>
void f3(F&& f)
{
std::invoke<F>(f, "hi");
}
int main()
{
f1([](const char*) {}); // ok
f2([](const char*) {}); // ok
f3([](const char*) {}); // error
}
cppreference says the following about std::invoke:
Invoke the Callable object f with the parameters args. As by INVOKE(std::forward<F>(f), std::forward<Args>(args)...). This overload participates in overload resolution only if std::is_invocable_v<F, Args...> is true.
So why is f3 not equivalent to f1?
std::invoke is itself a function. In your case, its first parameter is a rvalue reference while f is a lvalue, so the error occurs.
INVOKE(std::forward<F>(f), std::forward<Args>(args)...) is executed after the function std::invoke is properly selected and called. Basically, your lambda function is passed as follows:
original lambda in main -> the parameter of f3 -> the parameter of std::invoke -> the parameter of INVOKE
So the std::forward in INVOKE(std::forward<F>(f), std::forward<Args>(args)...) is used in the last step, while you need to forward the lambda in the middle step (the parameter of f3 -> the parameter of std::invoke). I guess this is where your confusion comes.
Because you need to std::forward<F>(f) to std::invoke():
template <typename F>
void f3(F&& f)
{
std::invoke<F>(std::forward<F>(f), "hi"); // works
}
Consider the difference between these two calls:
void f(const int&) { std::cout << "const int&" << std::endl; }
void f(int&&) { std::cout << "int&&" << std::endl; }
int main()
{
std::cout << "first" << std::endl;
int&& a = 3;
f(a);
std::cout << "second" << std::endl;
int&& b = 4;
f(std::forward<int>(b));
}
The output is
first
const int&
second
int&&
If you remove the const int& overload, you even get a compiler error for the first call:
error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'
The std::forward() is necessary for passing the correct type to std::invoke().
I guess you're getting this error:
note: template argument deduction/substitution failed:
note: cannot convert ‘f’ (type ‘main()::<lambda(const char*)>’) to type ‘main()::<lambda(const char*)>&&’
That's because inside the function f3, f is an L-value, but the invoke expects an R-value. For template argument deduction/substitution to work, the types have to match EXACTLY.
When you perfect forward f to invoke, this issue is resolved as you passed an R-value originally from outside f3.

Universal Reference and constness

This C++ code doesn't compile, you get an error "candidate function template not viable: 1st argument ('const int32_t' (aka 'const int')) would lose const qualifier"
I know I can solve this by adding an overload for Func(const T& value), but I'm curious to learn why this doesn't compile?
template <typename T>
void Func(T&& value)
{
// Do stuff
}
struct Obj
{
int32_t Id{};
};
int main(int argc, char* argv[])
{
const Obj i{};
Func<int32_t>(i.Id);
}
When you make this call:
Func<int32_t>(i.Id);
you are specifying the template arguments. This means the T&& in Func is not considered a forwarding reference at all. Instead, it's just an rvalue-reference, which is int32_t&&. As the compiler says, binding int32_t && to a int32_t const & would discard the const qualifier, and the call doesn't compile.
On the other hand, if you don't specify the template argument:
Func(i.Id);
then the T&& is indeed a forwarding reference, which deduces int32_t const &, and the call compiles.

invoke_result_t<> not matching lambda with a reference parameter

Using a function that accepts templated functions works great when the type is either an rvalue reference or has no reference, but as soon as I make it an lvalue reference it breaks.
Note that V is currently unused here, but it still fails to compile anyways regardless of whether it's used or not.
using namespace std;
template <typename F, typename V = std::invoke_result_t<F, string>>
void func(F f) {
std::vector<string> v = { "a", "b", "c" };
std::for_each(v.begin(), v.end(), f);
}
int main() {
func([](string s) { return s.length(); }); // Good
// func([](string& s) { return s.length(); }); // Bad
func([](const string& s) { return s.length(); }); // Good
}
main.cpp: In function 'int main()':
main.cpp:18:46: error: no matching function for call to 'func(main()::)'
func([](string& s) { return s.length(); });
^
main.cpp:11:6: note: candidate: 'template void func(F)'
void func(F f) {
^~~~
main.cpp:11:6: note: template argument deduction/substitution failed:
I can't do something like
std::invoke_result_t<F, string&>
and I couldn't do something like
std::invoke_result_t<F, std::add_lvalue_reference_t<string>>
The last one was a shot in the dark. My template knowledge is not that great. I've been searching around on here and on various blogs/google/etc, haven't had much success.
std::invoke_result_t<F, string>
this means passing F a string rvalue. And you cannot if F takes an lvalue reference.
I can't do something like
std::invoke_result_t<F, string&>
well yes you can. Do that if you want to know what the result of calling it with a non-const lvalue is.
At your point of use in your sample code, you pass it an lvalue. The string&& overload does not work.

template specialization for std::atomic<double> &

I have this MCVE:
#include <stdio.h>
#include <atomic>
template<typename T> void assertVariableHasBeenSet( T, const char * );
template<> void assertVariableHasBeenSet<std::atomic<double> &>
( std::atomic<double> & myDouble,
const char * variableName
)
{
printf( "Double:%s=%f\n", variableName, myDouble.load() );
};
int main()
{
std::atomic<double> myDoubleAtomic {23.45};
assertVariableHasBeenSet( myDoubleAtomic, "myDoubleAtomic" );
}
I get this compiler error:
getType.cpp: In function ‘int main()’:
getType.cpp:14:61: error: use of deleted function ‘std::atomic<_Tp>::atomic(const std::atomic<_Tp>&) [with _Tp = double]’
assertVariableHasBeenSet( myDoubleAtomic, "myDoubleAtomic" );
^
In file included from getType.cpp:2:0:
/usr/local/include/c++/4.9.4/atomic:169:7: note: declared here
atomic(const atomic&) = delete;
^
getType.cpp:4:27: error: initializing argument 1 of ‘void assertVariableHasBeenSet(T, const char*) [with T = std::atomic<double>]’
How can I pass a std::atomic<double> reference to the specialized template?
In a normal function it is possible.
For this case, T will be deduced as std::atomic<double>, not std::atomic<double> &. Then the primary template will always be invoked instead of the specialization.
You can specify the template argument explicitly, e.g.
assertVariableHasBeenSet<std::atomic<double> &>(myDoubleAtomic, "myDoubleAtomic");
Or apply overloading.
template<typename T> void assertVariableHasBeenSet( T, const char * );
void assertVariableHasBeenSet( std::atomic<double> & myDouble,
const char * variableName
)
{
printf( "Double:%s=%f\n", variableName, myDouble.load() );
}
Your issue is here:
template<typename T> void assertVariableHasBeenSet( T, const char * );
The primary template will be chosen because myDoubleAtomic is of type std::atomic<double>, not std::atomic<double> &.
The primary template tries to pass T by value, requiring a copy. std::atomic has a deleted copy constructor resulting in that error.
You should tell the compiler what type to use explicitly :
assertVariableHasBeenSet<std::atomic<double> &>(myDoubleAtomic, "myDoubleAtomic" );
The first thing to happen is overload resolution. During overload resolution the type T is deduced as std::atomic<double>. Next the proper specialisation is determined. There is no specialised version and the primary template is used. The specialisation for std::atomic<double>& will never be found by deduction.
There are two approaches to fix the problem (I don’t consider specifying the type explicitly a solution):
Declare the primary template to take a forwarding reference T&& as this would deduce T as std::atomic<double>&.
Instead of template specialisation use overloading, i.e., remove the template<> and the <std::atomic<double>&> after the function name.