Мне нужно сделать асинхронный вызов внутри лямбды, и как только асинхронный вызов завершится, я должен вызвать саму лямбду.
Я пытаюсь объяснить свою проблему с кодом:
typedef function<void(int id)> Callback;
AsyncWork1(Callback call, int id, string)
{
//...
call(id);
}
AsyncWork2(Callback call, int id, double, string)
{
//...
call(id);
}
void AsyncWorks(Callback final_callback, int id)
{
Callback lambda = [&lambda, final_callback, id](int next_work) -> void
{
if (next_work == 1)
{
//...
AsyncWork1(lambda, 2, "bla bla");
}
else if (next_work == 2)
{
//...
//the lambda variable no longer exists
AsyncWork2(lambda, 3, 0.0, "bla bla");
}
else if (next_work == 3)
{
//...
final_callback(id);
}
};
lambda(1);
}
int main()
{
AsyncWorks(...);
AsyncWorks(...);
AsyncWorks(...);
AsyncWorks(...);
return 0;
}
Проблема в том, что когда код выходит из функции "AsyncWorks(...)", локальная переменная "лямбда" больше не существует.
Я прочитал несколько тем, в которых говорится о лямбда-рекурсии, но я не нашел никакого решения.
Как я могу решить эту проблему?
Имейте в виду, что у этой конструкции есть недостатки. Невозможно узнать, что все асинхронные задачи завершены к тому времени, когда вы return 0;
из main
. Это можно реализовать, но вы можете просто использовать std::async
и полагаться на std::future
, который он предоставляет.
Я знаю, это простой пример, в реальном приложении (на базе FreeRTOS) такого не происходит.
Основная проблема заключается в том, что C++ не предоставляет указатель this
лямбды самому себе.
Так уж сложилось, что во многих языках при определении чего-либо нельзя ссылаться на себя. Это фиксируется в функциональных языках с помощью метода, называемого «Комбинатор Y».
Простой комбинатор y в C++ выглядит так:
template<class F>
struct y_combinator_t {
F f;
template<class...Args>
auto operator()(Args&&...args)
-> std::result_of_t< F&( y_combinator_t<F>&, Args&&... ) >
{
return f( *this, std::forward<Args>(args)... );
}
};
template<class F>
y_combinator_t<std::decay_t<F>> y_combinate( F&& f ) {
return {std::forward<F>(f)};
}
У меня два мнения, стоит ли нам f( *this
или f( f
, я иногда делаю и то, и другое.
Использовать:
void AsyncWorks(Callback final_callback, int id)
{
Callback lambda = y_combinate(
[final_callback, id]
(auto& self, int next_work)
-> void
{
if (next_work == 1) {
//...
AsyncWork1(self, 2, "bla bla");
} else if (next_work == 2) {
//...
//the lambda variable no longer exists
AsyncWork2(self, 3, 0.0, "bla bla");
} else if (next_work == 3) {
//...
final_callback(id);
}
}
);
lambda(1);
}
по сути, я добавил неявный параметр self
в тело лямбда-функции. Вызывающий operator()
не видит этот параметр.
Комбинатор Y основан на этот пост от меня с модификациями.
y_combinate
-> y_combine
?
Что означает "F&"?
@parmi F — это тип, F& — ссылка на этот тип. F&( y_combinator_t<F>&, Args&&... )
— это сигнатура функции, возвращающей F&
и принимающей y_combinator_t<F>&, Args&&...
. Он передается в result_of_t
, который делает вид, что возвращаемый тип является типом объекта, которому вы передаете аргументы, и возвращает фактически возвращаемый тип. Таким образом, мы получаем «если вы вызываете F&
с y_combinator_t<F>&, Args&&...
, любой тип, который возвращается», является типом возврата y_combinator_t<F>::operator()(Args&&...)
.
@parmi все это технические детали; дело в том, что вы пишете свою лямбду с auto&self
в качестве первого аргумента, а затем передаете ее y_combinate
, и теперь у вас есть доступ к ссылке на себя.
Lambda может захватить себя неявно. Демонстрировать как? см. ниже код, в котором вычисляется факторное значение.
#include <iostream>
int (* factorial)( const int) = []( const int number)
{
if ( number > 1)
{
return number* factorial( number - 1);
}
else
{
return 1;
}
};
int main(int , char *[])
{
int fact = factorial( 7);
std::cout<< "7! = "<< fact<< std::endl;
}
Output 7! = 5040
Если какая-либо переменная используется внутри лямбды, то лямбда захватывает ее неявно, если она не захвачена явно. Благодаря этому внутри лямбды доступно имя factorial
, ссылающееся на себя.
Но если вместоint (* factorial)( const int) = []( const int number){//implementation };
, если auto
используется следующим образом,auto factorial = []( const int number){ //implementation };
то компилятор g++ выдает следующую ошибку,error: use of ‘factorial’ before deduction of ‘auto’
return number* factorial( number - 1);
Это потому, что тип factorial
не выводится, auto не выводит для того же элемента управления блокировать. Имя factorial
будет доступно только под объявлением в случае auto
.
Итак, в заключение, это работает, только если лямбда ничего не фиксирует.
Если лямбда захватывает что-либо, то его нельзя преобразовать в указатель на функцию, потому что он становится классом с уникальным именем, известным только компилятору, а затем auto
должен использовать, а затем то, что произойдет, объяснено выше. Но это не цель лямбда, так как цель std::map
состоит в том, чтобы предоставить пару ключ/значение, а не случайный доступ, аналогично цель лямбда состоит в том, чтобы сделать быстрые анонимные функции обратного вызова с возможностью захвата.
Ваш factorial
тоже глобальный :-/
@Якк Чтобы понять ваш ответ, мне пришлось потратить некоторое время на изучение многих функций С++, категории значений, rvalue, lvalue, конструктора перемещения, оператора присваивания перемещения, вариативных шаблонов, вариативных шаблонов с неявными преобразованиями, result_of_t<>, распада_t<>, вперед<>.
Но у меня пока ничего нет, зачем ты поставил здесь символ "&"?
... std::result_of_t <F&(y_combinator_t<...
Я также переписал ваше решение, чтобы сделать его более конкретным для моего случая и более легким для чтения и понимания для меня (и для всех тех, кто только начинает работать с С++),
class y_combinator_t
{
public:
function<void(y_combinator_t*, int, double, string)> callback;
void operator()(int a, double b, string s)
{
this->callback(this, a, b, s);
}
};
y_combinator_t combinator = {
[id_work = 1]
(y_combinator_t* _this, int a, double b, string s) mutable -> void
{
if (id_work == 1)
{
//...
AsyncWork1(*_this, 2, 3.0, "bla bla");
id_work = 2;
}
else if (id_work == 2)
{
//...
AsyncWork2(*_this, 3, 0.0, "bla bla");
id_work = 3;
}
else if (id_work == 3)
{
//...
}
}
};
//Start works
combinator(0, 0, "");
Возможно, вы могли бы сделать это и удалить его в конце.