无法从lambda函数中推导出模板参数std :: function

oma*_*mar 2 c++ lambda templates c++11 c++17

在使用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;
}
Run Code Online (Sandbox Code Playgroud)

要编译这个程序,我使用的是GNU C++编译器 g ++:

$ g++ --version // g++ (Ubuntu 6.5.0-1ubuntu1~16.04) 6.5.0 20181026
Run Code Online (Sandbox Code Playgroud)

编译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);
                ^
Run Code Online (Sandbox Code Playgroud)

(C++ 14C++ 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;
}
Run Code Online (Sandbox Code Playgroud)

然而,当我使用其他类型的,而不是intdouble,float,char,或者bool,它可以作为很好,这让我更加困惑.

所以,我的问题如下:

  • 当我明确指定int(和其他人)作为模板参数时,为什么它可以工作?
  • 有没有更通用的方法来解决这个问题?

Yak*_*ont 7

A std::function不是lambda,而lambda不是lambda std::function.

lambda是一个匿名类型,带有operator()一些其他小实用程序.您的:

auto foo = [](int i) {
    std::cout << i << std::endl;
};
Run Code Online (Sandbox Code Playgroud)

是简写

struct __anonymous__type__you__cannot__name__ {
  void operator()(int i) { 
    std::cout << i << std::endl;
  }
};
__anonymous__type__you__cannot__name__ foo;
Run Code Online (Sandbox Code Playgroud)

非常粗略(有实际的转换功能指针和其他一些我不会涉及的噪音).

但是,请注意,它并没有继承std::function<void(int)>.


lambda不会推导出a的模板参数,std::function因为它们是不相关的类型.模板类型推导是针对传递的参数类型及其基类的精确模式匹配.它不会尝试使用任何类型的转换.


A std::function<R(Args...)>是一种类型,可以存储任何可复制的类型,可以使用与之兼容的值调用Args...并返回与之兼容的内容R.

所以std::function<void(char)>可以存储任何可以用a调用的东西char.因为int函数可以使用a来调用char,这是有效的.

试试吧:

void some_func( int x ) {
  std::cout << x << "\n";
}
int main() {
  some_func('a');
  some_func(3.14);
}
Run Code Online (Sandbox Code Playgroud)

std::function 这是从它的签名到存储在其中的可调用的一些转换.


最简单的解决方案是:

template <class F, class T>
void call(F f, T v) {
  f(v);
}
Run Code Online (Sandbox Code Playgroud)

现在,在极少数情况下,您实际上需要签名.您可以在执行此操作:

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));
}
Run Code Online (Sandbox Code Playgroud)

最后,你callstd::invoke来自的残缺版本.考虑使用它; 如果没有,请使用backported版本.