Как лямбда может захватить себя для асинхронного вызова?

Мне нужно сделать асинхронный вызов внутри лямбды, и как только асинхронный вызов завершится, я должен вызвать саму лямбду.

Я пытаюсь объяснить свою проблему с кодом:

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(...)", локальная переменная "лямбда" больше не существует.

Я прочитал несколько тем, в которых говорится о лямбда-рекурсии, но я не нашел никакого решения.

Как я могу решить эту проблему?

Возможно, вы могли бы сделать это и удалить его в конце.

NathanOliver 29.05.2019 16:54

Имейте в виду, что у этой конструкции есть недостатки. Невозможно узнать, что все асинхронные задачи завершены к тому времени, когда вы return 0; из main. Это можно реализовать, но вы можете просто использовать std::async и полагаться на std::future, который он предоставляет.

François Andrieux 29.05.2019 17:05

Я знаю, это простой пример, в реальном приложении (на базе FreeRTOS) такого не происходит.

Parminder Singh 30.05.2019 10:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
3
553
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Основная проблема заключается в том, что 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?
Caleth 29.05.2019 17:44

Что означает "F&"?

Parminder Singh 03.06.2019 14:13

@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&&...).

Yakk - Adam Nevraumont 03.06.2019 15:06

@parmi все это технические детали; дело в том, что вы пишете свою лямбду с auto&self в качестве первого аргумента, а затем передаете ее y_combinate, и теперь у вас есть доступ к ссылке на себя.

Yakk - Adam Nevraumont 03.06.2019 15:52

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.

Итак, в заключение, это работает, только если лямбда ничего не фиксирует.

alain 29.05.2019 18:16

Если лямбда захватывает что-либо, то его нельзя преобразовать в указатель на функцию, потому что он становится классом с уникальным именем, известным только компилятору, а затем auto должен использовать, а затем то, что произойдет, объяснено выше. Но это не цель лямбда, так как цель std::map состоит в том, чтобы предоставить пару ключ/значение, а не случайный доступ, аналогично цель лямбда состоит в том, чтобы сделать быстрые анонимные функции обратного вызова с возможностью захвата.

Vikas Awadhiya 29.05.2019 20:33

Ваш factorial тоже глобальный :-/

Jarod42 29.05.2019 22:00

@Якк Чтобы понять ваш ответ, мне пришлось потратить некоторое время на изучение многих функций С++, категории значений, 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, "");

Другие вопросы по теме