Почему C ++ 11 неявно не преобразует лямбды в объекты std :: function?

Я реализовал общий класс генератора событий, который позволяет коду регистрировать обратные вызовы и генерировать события с аргументами. Я использовал стирание типа 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;
}

12

Решение

Лямбда это не 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 ++ в templates не работает на уровне «можешь конвертировать в» — это сопоставление с образцом, чистое и простое. Поскольку лямбда-это тип, не связанный с любым std::functionнет std::function тип может быть выведен из него.

Если бы C ++ попытался сделать это в общем случае, ему пришлось бы инвертировать процесс, завершаемый по Тьюрингу, чтобы определить, какой (если он есть) набор типов можно было бы передать template чтобы сгенерировать экземпляр, совместимый с конверсией.

Теоретически, мы могли бы добавить «оператор вывести аргументы шаблона из» в язык, где реализаторы данного template может написать код, который принимает какой-то произвольный тип, и они пытаются «выявить» из этого типа, что template параметры должны быть использованы для экземпляра «. Но в C ++ этого нет.

11

Другие решения

Компилятор ничего не выводит, потому что компилятор реализует язык C ++, а правила вывода аргументов шаблонов языка не допускают вывода так, как вы хотите.

Вот простой пример, который представляет вашу ситуацию:

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

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

int main()
{
magic(10);   // what is T?
}
6

когда boost::any хранит значение, он использует статический тип этого объекта, чтобы определить, какой тип объекта хранится. Вы можете только разыграть any вернуться к объекту правильного типа, если вы укажете статический тип того, что хранится.

Каждая лямбда C ++ связана с типом, определяемым реализацией, который непрозрачен для пользователя. Хотя лямбды могут быть вызваны как функции, они напрямую не оцениваются как std::functions. Приведение необходимо при хранении лямбда в any чтобы гарантировать, что статический тип того, что хранится, является std::function когда ты откинешь.

Надеюсь это поможет!

1
По вопросам рекламы [email protected]