在探索C ++模板时,我偶然发现了以下代码中的示例:
#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;
}
要编译该程序,我正在使用 GNU C ++编译器 g ++:
$ g++ --version // g++ (Ubuntu 6.5.0-1ubuntu1~16.04) 6.5.0 20181026
为 C ++ 11 编译后,出现以下错误:
$ 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);
^
(与 C ++ 14 和 C ++ 17 相同)
从编译器错误和注释中我了解到,编译器无法推断lambda的类型,因为它无法与std :: function相匹配。
看着关于此错误的先前问题(1,2,3和4),我仍然对此感到困惑。
如问题3和4的答案所指出,可以通过显式指定模板参数来解决此错误,如下所示:
int main(int argc, char const *argv[])
{
...
call<int>(foo, 1); // <-- specify template argument type
// call<double>(foo, 1) // <-- works! Why?
return 0;
}
但是,当我使用其他类型而不是int
时,例如double
,float
,char
或bool
,它也可以工作,让我更加困惑。
所以,我的问题如下:
int
(及其他)作为模板参数时,为什么它起作用?答案 0 :(得分:5)
std::function
不是lambda,lambda不是std::function
。
lambda是具有operator()
和其他一些次要实用程序的匿名类型。您的:
auto foo = [](int i) {
std::cout << i << std::endl;
};
是
的简写struct __anonymous__type__you__cannot__name__ {
void operator()(int i) {
std::cout << i << std::endl;
}
};
__anonymous__type__you__cannot__name__ foo;
非常粗略(有实际的转换为函数的指针以及一些我不会介绍的其他杂物)。
但是请注意,它不是从std::function<void(int)>
继承的。
lambda不会推论std::function
的模板参数,因为它们是不相关的类型。模板类型推导是针对传递的参数类型及其基类的精确模式匹配。它不会尝试使用任何形式的转换。
std::function<R(Args...)>
是一种类型,它可以存储任何可复制的内容,这些可复制内容可以使用与Args...
兼容的值来调用,并返回与R
兼容的内容。
因此std::function<void(char)>
可以存储可以用char
调用的任何。由于int
的功能可以通过char
来调用,因此可以正常工作。
尝试一下:
void some_func( int x ) {
std::cout << x << "\n";
}
int main() {
some_func('a');
some_func(3.14);
}
std::function
完成了从签名到存储在其中的可调用对象的转换。
最简单的解决方案是:
template <class F, class T>
void call(F f, T v) {
f(v);
}
现在,在极少数情况下,您实际上需要签名。您可以在c++17中进行此操作:
template<class T>
void call(std::function<void(T)> f, T v) {
f(v);
}
template<class F, class T>
void call(F f_in, T v) {
std::function f = std::forward<F>(f_in);
call(std::move(f), std::forward<T>(v));
}
最后,您的call
是c++17中std::invoke
的残废版本。考虑使用它;如果没有,请使用向后移植的版本。