【问题标题】:Failure to deduce template argument std::function from lambda function无法从 lambda 函数推导出模板参数 std::function
【发布时间】:2018-11-15 18:57:25
【问题描述】:

在探索 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++14C++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(和其他)作为模板参数时它会起作用?
  • 有没有更通用的方法来解决这个问题?

【问题讨论】:

  • 为什么是std::function?为什么不只是T f
  • @tkausl 和T f,我将无法将f 用作函数。
  • @omar tkausl 可能是指F f

标签: c++ c++11 templates lambda c++17


【解决方案1】:

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&lt;void(int)&gt;


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


std::function&lt;R(Args...)&gt; 是一种可以存储任何可复制的类型,可以使用与 Args... 兼容的值调用并返回与 R 兼容的内容。

所以std::function&lt;void(char)&gt; 可以存储可以用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));
}

最后,您的call 是来自std::invoke 的残缺版本。考虑使用它;如果没有,请使用反向移植版本。

【讨论】:

  • 是的,应该这样做。正要写同样的东西。
  • 此外,显式转换有效,因为它不再是模板,因此可以启动隐式转换。
  • 如果你要使用std::forward,你应该使用F&amp;&amp;T&amp;&amp;
  • @tkausl 如果有人显式传递模板参数,std::move 将是错误的。 std::forward 无论如何都是正确的。我考虑过使用std::move,但只有约定保证FT 不是引用,所以我选择了正确性而不是简单性。
  • @Yakk-AdamNevraumont 感谢您的彻底回答。我有兴趣将 lambda 传递给我的 cal()l 函数,并且由于 lambda 是 callable,我认为传递它的最佳方法是将其包装在 std::function(Instances of std::function can store, copy, and invoke any Callable target)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多