T240104 Failed to Passing a Lambda Expression to std::function
这是昨天群里的一个问题,但我想谈的是背后的本质问题。
这个问题其实可以简化为:
template <typename ...Args>
void foo(std::function<bool(Args...)>) { }
int main() {
// error: no matching function for call to 'foo(main()::<lambda(int)>)'
foo([](int) { return true; });
}
本质原因在 TAD(模板参数推导),这里实参 A = closure type
,形参 P = std::function<bool(Args...)>
。由于类型完全不同,P
无法替换成 A
,这里 TAD 完全不可能推断出 Args...
的类型。于是,模板函数因模板替换失败,而被早早移除,连一级筛选 Candidate functions 都未能进入。
不用 std::function
可能更易理解。一个例子:
template <class T> struct A {};
template <class T> struct B : A<T> {}
struct C {};
template <class T>
void f(A<T>) {}
int main() {
// error: no matching function for call to 'f(C)'
f(C{});
}
template <class T> struct A {};
template <class T> struct B : A<T> {};
struct C {};
template <class T>
void f(A<T>) {}
int main() {
f(A<int>{}); // OK
f(B<int>{}); // OK
f(C{}); // Error: no matching function for call to 'f(C)'
}
那么如何解决这个问题呢?
第一种方式,显式指定模板参数。
template <class T>
void foo(std::function<bool(T)>) { }
int main() {
// OK
foo<int>([](int) { return true; });
}
之所以能成功,是因为这样跳过了 Template Handling 这一步,模板并不用进行推导和替换,模板函数最终进入 Viable functions,通过隐式转换最终在绝胜局胜出。
但是不再支持 Variadic templates,那样还是会发生 TAD。
第二种方式,弃用 std::function
。
void foo(auto f) { }
int main() {
// OK
foo([](int) { return true; });
}
这里使用 Abbreviated function template 进一步简化了模板参数的写法,此时参数推导和替换将不会发生类型无关系的情况,这也是更好的一种方式。
除非你要保存多个 Callback,例如 std::vector<std::function>
,否则没必要使用 std::function
,它使用了类型擦除,开销比直接使用 Lambda function 要大。甚至在保存多个 Callback 时,有些优化也会消除 std::function
以提高性能.
消除之后咋用?那得配合一个新工具 function_traits
,而这个工具的实现,三言两语,难以述尽,留待后续。