While exploring templates in C++, I stumbled upon the example in the following code:
#include <iostream>
#include <functional>
template <typename T>
void call(std::function<void(T)> f, T v)
{
f(v);
}
int main(int argc, char const *argv[])
{
auto foo = [](int i) {
std::cout << i << std::endl;
};
call(foo, 1);
return 0;
}
To compile this program, I am using the GNU C++ Compiler g++:
$ g++ --version // g++ (Ubuntu 6.5.0-1ubuntu1~16.04) 6.5.0 20181026
After compiling for C++11, I get the following error:
$ g++ -std=c++11 template_example_1.cpp -Wall
template_example_1.cpp: In function ‘int main(int, const char**)’:
template_example_1.cpp:15:16: error: no matching function for call to ‘call(main(int, const char**)::<lambda(int)>&, int)’
call(foo, 1);
^
template_example_1.cpp:5:6: note: candidate: template<class T> void call(std::function<void(T)>, T)
void call(std::function<void(T)> f, T v)
^~~~
template_example_1.cpp:5:6: note: template argument deduction/substitution failed:
template_example_1.cpp:15:16: note: ‘main(int, const char**)::<lambda(int)>’ is not derived from ‘std::function<void(T)>’
call(foo, 1);
^
(same for C++14 and C++17)
From the compiler error and notes I understand that the compiler failed to deduce the type of the lambda, since it cannot be matched against std::function.
Looking at previous questions (1, 2, 3, and 4) regarding this error, I am still confused about it.
As pointed out in answers from questions 3 and 4, this error can be fixed by explicitly specifying the template argument, like so:
int main(int argc, char const *argv[])
{
...
call<int>(foo, 1); // <-- specify template argument type
// call<double>(foo, 1) // <-- works! Why?
return 0;
}
However, when I use other types instead of int
, like double
, float
, char
, or bool
, it works as well, which got me more confused.
So, my questions are as follow:
- Why does it work when I explicitly specify
int
(and others) as the template argument? - Is there a more general way to solve this?
A
std::function
is not a lambda, and a lambda is not astd::function
.A lambda is an anonymous type with an
operator()
and some other minor utility. Your:is shorthand for
very roughly (there are actual convert-to-function pointer and some other noise I won't cover).
But, note that it does not inherit from
std::function<void(int)>
.A lambda won't deduce the template parameters of a
std::function
because they are unrelated types. Template type deduction is exact pattern matching against types of arguments passed and their base classes. It does not attempt to use conversion of any kind.A
std::function<R(Args...)>
is a type that can store anything copyable that can be invoked with values compatible withArgs...
and returns something compatible withR
.So
std::function<void(char)>
can store anything that can be invoked with achar
. Asint
functions can be invoked with achar
, that works.Try it:
std::function
does that some conversion from its signature to the callable stored within it.The simplest solution is:
now, in extremely rare cases, you actually need the signature. You can do this in c++17:
Finally, your
call
is a crippled version ofstd::invoke
from c++17. Consider using it; if not, use backported versions.