无法从lambda函数

时间:2018-11-15 18:57:25

标签: c++ c++11 templates lambda 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;
}

要编译该程序,我正在使用 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相匹配。

看着关于此错误的先前问题(1234),我仍然对此感到困惑。

如问题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时,例如doublefloatcharbool,它也可以工作,让我更加困惑。

所以,我的问题如下:

  • 当我显式指定int(及其他)作为模板参数时,为什么它起作用?
  • 是否有更通用的解决方法?

1 个答案:

答案 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);
}

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

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));
}

最后,您的callstd::invoke的残废版本。考虑使用它;如果没有,请使用向后移植的版本。