这是昨天群里的一个问题,但我想谈的是背后的本质问题。

这个问题其实可以简化为:

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,而这个工具的实现,三言两语,难以述尽,留待后续。

Leave a Reply

Your email address will not be published. Required fields are marked *

You can use the Markdown in the comment form.