Я реализовал общий класс генератора событий, который позволяет коду регистрировать обратные вызовы и генерировать события с аргументами. Я использовал стирание типа Boost.Any для хранения обратных вызовов, чтобы они могли иметь произвольные сигнатуры параметров.
Это все работает, но по какой-то причине входящие лямбды должны быть сначала превращены в std::function
объекты. Почему компилятор не делает вывод, что лямбда является типом функции? Это из-за того, как я использую шаблоны Variadic?
Я использую 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;
}
Лямбда это не std::function
, а также std::function
это не лямбда
Лямбда является синтаксическим сахаром для создания анонимного класса, который выглядит следующим образом:
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
на самом деле быть неким именем типа.
std::function
это совершенно другая вещь. Это объект, который реализует operator()
с определенной сигнатурой, и хранит интеллектуальный указатель семантики значения на абстрактный интерфейс, который охватывает операции копирования / перемещения / вызова. Оно имеет template
конструктор d, который может принимать любой тип, который поддерживает копирование / перемещение /operator()
с совместимой подписью генерирует конкретный пользовательский класс, который реализует абстрактный внутренний интерфейс, и сохраняет его в упомянутом выше интеллектуальном указателе внутренней семантики значения.
Затем он перенаправляет операции из себя как тип значения в абстрактный внутренний указатель, включая совершенную пересылку в метод вызова.
Как это происходит, вы Можно хранить лямбду в std::function
Так же, как вы можете сохранить указатель на функцию.
Но есть целый ряд разных std::function
который может хранить данную лямбду — что-нибудь где типы, которые можно преобразовать в аргументы и из аргументов, работают и фактически работают одинаково хорошо, насколько std::function
обеспокоен.
Вывод типа в C ++ в template
s не работает на уровне «можешь конвертировать в» — это сопоставление с образцом, чистое и простое. Поскольку лямбда-это тип, не связанный с любым std::function
нет std::function
тип может быть выведен из него.
Если бы C ++ попытался сделать это в общем случае, ему пришлось бы инвертировать процесс, завершаемый по Тьюрингу, чтобы определить, какой (если он есть) набор типов можно было бы передать template
чтобы сгенерировать экземпляр, совместимый с конверсией.
Теоретически, мы могли бы добавить «оператор вывести аргументы шаблона из» в язык, где реализаторы данного template
может написать код, который принимает какой-то произвольный тип, и они пытаются «выявить» из этого типа, что template
параметры должны быть использованы для экземпляра «. Но в C ++ этого нет.
Компилятор ничего не выводит, потому что компилятор реализует язык C ++, а правила вывода аргументов шаблонов языка не допускают вывода так, как вы хотите.
Вот простой пример, который представляет вашу ситуацию:
template <typename T> struct Foo
{
Foo(int) {}
};
template <typename T> void magic(Foo<T> const &);
int main()
{
magic(10); // what is T?
}
когда boost::any
хранит значение, он использует статический тип этого объекта, чтобы определить, какой тип объекта хранится. Вы можете только разыграть any
вернуться к объекту правильного типа, если вы укажете статический тип того, что хранится.
Каждая лямбда C ++ связана с типом, определяемым реализацией, который непрозрачен для пользователя. Хотя лямбды могут быть вызваны как функции, они напрямую не оцениваются как std::function
s. Приведение необходимо при хранении лямбда в any
чтобы гарантировать, что статический тип того, что хранится, является std::function
когда ты откинешь.
Надеюсь это поможет!