19

我实现了一个通用的事件发射器类,它允许代码注册回调,并发出带有参数的事件。我使用 Boost.Any 类型擦除来存储回调,以便它们可以具有任意参数签名。

这一切都有效,但由于某种原因,传入的 lambda 必须首先转换为std::function对象。为什么编译器不推断 lambda 是函数类型?是因为我使用可变参数模板的方式吗?

我使用 Clang(版本字符串:)Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)

代码:

#include <functional>
#include <iostream>
#include <map>
#include <string>
#include <vector>

#include <boost/any.hpp>


using std::cout;
using std::endl;
using std::function;
using std::map;
using std::string;
using std::vector;


class emitter {

   public:

      template <typename... Args>
      void on(string const& event_type, function<void (Args...)> const& f) {
         _listeners[event_type].push_back(f);
      }

      template <typename... Args>
      void emit(string const& event_type, Args... args) {
         auto listeners = _listeners.find(event_type);
         for (auto l : listeners->second) {
            auto lf = boost::any_cast<function<void (Args...)>>(l);
            lf(args...);
         }
      }

   private:

      map<string, vector<boost::any>> _listeners;

};


int main(int argc, char** argv) {

   emitter e;

   int capture = 6;

   // Not sure why Clang (at least) can't deduce the type of the lambda. I don't
   // think the explicit function<...> business should be necessary.
   e.on("my event",
        function<void ()>( // <--- why is this necessary?
           [&] () {
              cout << "my event occurred " << capture << endl;
           }));
   e.on("my event 2",
        function<void (int)>(
           [&] (int x) {
              cout << "my event 2 occurred: " << x << endl;
           }));
   e.on("my event 3",
        function<void (double)>(
           [&] (double x) {
              cout << "my event 3 occurred: " << x << endl;
           }));
   e.on("my event 4",
        function<void (int, double)>(
           [&] (int x, double y) {
              cout << "my event 4 occurred: " << x << " " << y << endl;
           }));

   e.emit("my event");
   e.emit("my event 2", 1);
   e.emit("my event 3", 3.14159);
   e.emit("my event 4", 10, 3.14159);

   return EXIT_SUCCESS;
}
4

3 回答 3

14

lambda 不是 a std::functionstd::function也不是 lambda。

lambda 是创建匿名类的语法糖,如下所示:

struct my_lambda {
private:
  int captured_int;
  double captured_double;
  char& referenced_char;
public:
  int operator()( float passed_float ) const {
    // code
  }
};
int captured_int = 7;
double captured_double = 3.14;
char referenced_char = 'a';
my_lambda closure {captured_int, captured_double, referenced_char};
closure( 2.7f );

由此:

int captured_int = 7;
double captured_double = 3.14;
char referenced_char = 'a';
auto closure = [=,&referenced_char](float passed_float)->int {
  // code
};
closure(2.7);

类型名称my_lambda实际上是一些无法命名的类型。

Astd::function是完全不同的东西。它是一个operator()使用特定签名实现的对象,并将智能值语义指针存储到涵盖复制/移动/调用操作的抽象接口。它有一个templated 构造函数,可以采用任何支持复制/移动/operator()具有兼容签名的类型,生成一个实现抽象内部接口的具体自定义类,并将其存储在上述内部值语义智能指针中。

然后它将操作作为值类型从自身转发到抽象内部指针,包括完美转发到调用方法。

碰巧,您可以将 lambda 存储在 a 中std::function,就像您可以存储函数指针一样。

但是有无数种不同std::function的方法可以存储给定的 lambda ——任何类型可以与参数相互转换的东西都可以工作,而且实际上同样工作得很好,就std::function而言。

s中的 C++ 中的类型推导在template“你可以转换成”的级别上不起作用——它是模式匹配,纯粹而简单。由于 lambda 是与 any 无关的类型,因此无法从中推断出std::function任何类型。std::function

如果 C++ 在一般情况下尝试这样做,则必须反转图灵完备过程以确定可以将哪些(如果有)类型集传递给template以生成与转换兼容的实例。

理论上,我们可以将“操作符从模板参数推导出来”添加到语言中,给定的实现者template可以编写采用任意类型的代码,并尝试从这种类型中梳理出template应该使用哪些参数实例”。但是 C++ 没有这个。

于 2014-02-05T20:07:19.753 回答
5

编译器不会推断任何东西,因为编译器实现了 C++ 语言,并且该语言的模板参数推导规则不允许以您想要的方式进行推导。

这是一个代表您的情况的简单示例:

template <typename T> struct Foo
{
    Foo(int) {}
};

template <typename T> void magic(Foo<T> const &);

int main()
{
    magic(10);   // what is T?
}
于 2014-02-05T19:35:56.697 回答
1

存储值时boost::any,它使用该对象的静态类型来确定存储的对象类型。然后,如果您指定所存储内容的静态类型,则只能将其any转换回正确类型的对象。

每个 C++ lambda 都与对用户不透明的实现定义的类型相关联。尽管 lambdas 可以作为函数调用,但它们不会直接计算为std::functions。将 lambda 存储在 中时,强制转换是必要的,any以确保所存储的静态类型std::function在您转换回时是 for 。

希望这可以帮助!

于 2014-02-05T19:36:42.787 回答