Можно ли хранить лямбды с разными подписями в std :: vector и выполнять их (с соответствующими аргументами) в функции? - PullRequest
2 голосов
/ 09 января 2020

Я создаю класс, который позволяет мне хранить лямбды, которые должны быть выполнены (по порядку) в определенный момент в будущем.

class Promise{
private:
    //snip//
    std::vector<std::function<void()>> lchain;
public:
    //snip//
    void then(const std::function<void()> &f){
        if (this->resolved) {//If the promise is resolved we just call the newly added function, else we add it to the lchain queue that will be processed later
            f();
            return;
        }

        lchain.push_back(f);
    }
    void launch(){
        this->resolved = true;
        for (auto &fun: this->lchain)
            fun();
    }
}

Очевидно, что он будет работать только с лямбдами с подпись, подобная [&](){}, но некоторые задачи должны работать с произвольным числом параметров произвольных типов (параметры и типы известны заранее, когда функция добавляется в очередь).

Пример программы-драйвера, которая в данный момент работает:

int main(){
    Promise* p = new Promise([](){
        std::cout << "first" << std::endl;
    })->then([](){
        std::cout << "second" << std::endl;
    });
    Promise->launch(); //In my code promise chains are picked up by worker threads that will launch them.
}

Пример программы, которую я хотел бы выполнить:

int main(){
        Promise* p = new Promise([](){
            return 5;
        })->then([](int n){
            return n*n;
        })->then([](int n){
            std::cout << n << std::endl; //Expected output: 25
        });
        Promise->launch();
    }

То, что я пытаюсь сделать:

  • Хранение лямбда-выражений смешанных подписей в std :: vector
  • Выполнение вызова метода then () с аргументами, связанными с f
  • Заставить функцию then () возвращать результат f поэтому он может быть передан следующей лямбде в цепочке (желательно связать ее перед сохранением лямбды в векторе)

Я искал в stackoverflow весь день, но ближайший Я получил this , но я хотел бы, чтобы в методе then () можно было упростить код программы, поскольку было бы затруднительно связывать каждую лямбду перед вызовом метода then ().

1 Ответ

4 голосов
/ 09 января 2020

У меня есть кое-что, что я думаю, делает то, что вы хотите. Я начну с примера, а затем представлю реализацию.

int main(){
  Promise p([] {
    return 5;
  });
  p.then([](int n) {
    return n*n;
  }).then([](int n) {
    std::cout << n << '\n';
  });
  p.launch();

  struct A { int n; };
  struct B { int n; };
  struct C { int n; };

  Promise q([](A a, int n) {
    std::cout << "A " << a.n << ' ' << n << '\n';
    return B{2};
  });
  q.then([](B b) {
    std::cout << "B " << b.n << '\n';
    return C{3};
  }).then([](C c) {
    std::cout << "C " << c.n << '\n';
  });
  q.launch(A{1}, 111);

  Promise<B(A, int)> r([](auto a, int n) {
    std::cout << "A " << a.n << ' ' << n << '\n';
    return B{5};
  });
  r.then([](auto b) {
    std::cout << "B " << b.n << '\n';
    return C{6};
  }).then([](auto c) {
    std::cout << "C " << c.n << '\n';
  });
  r.launch(A{4}, 222);
}

Это выводит:

25
A 1 111
B 2
C 3
A 4 222
B 5
C 6

Некоторые недостатки:

  • Вызов then после выполнения обещания функция автоматически не вызывается. В этой ситуации все запутывается, и я даже не уверен, возможно ли это.
  • Вы не можете звонить then несколько раз с одним и тем же обещанием. Вы должны построить цепочку и вызвать then по результатам предыдущего then.

Если какой-либо из этих недостатков делает это непригодным для использования, вы можете перестать читать этот громадный ответ.


Первое, что нам нужно, это способ получить подпись лямбды. Это используется только для руководства по выводам, поэтому не обязательно, чтобы базовая концепция работала.

template <typename Func>
struct signature : signature<decltype(&Func::operator())> {};

template <typename Func>
struct signature<Func *> : signature<Func> {};

template <typename Func>
struct signature<const Func> : signature<Func> {};

template <typename Ret, typename... Args>
struct signature<Ret(Args...)> {
  using type = Ret(Args...);
};

template <typename Class, typename Ret, typename... Args>
struct signature<Ret (Class::*)(Args...)> : signature<Ret(Args...)> {};

template <typename Class, typename Ret, typename... Args>
struct signature<Ret (Class::*)(Args...) const> : signature<Ret(Args...)> {};

template <typename Func>
using signature_t = typename signature<Func>::type;

Следующее, что нам нужно, это базовый класс. Мы знаем, что следующее обещание должно принимать тип возврата текущего обещания в качестве аргумента. Итак, мы знаем тип аргумента следующего обещания. Однако мы не знаем, что вернется к следующему обещанию до тех пор, пока не будет вызвано then, поэтому нам понадобится полиморфная c база для ссылки на следующее обещание.

template <typename... Args>
class PromiseBase {
public:
  virtual ~PromiseBase() = default;
  virtual void launch(Args...) = 0;
};

Теперь у нас есть Promise класс сам. Вы можете построить обещание с помощью функции. Как я упоминал выше, в обещании хранится указатель на следующее обещание в цепочке. then создает обещание из данной функции и сохраняет указатель на него. Существует только один указатель next, поэтому вы можете вызвать then только один раз. Есть утверждение, что этого не произойдет. launch вызывает сохраненную функцию и передает результат следующему обещанию в цепочке (если оно есть).

template <typename Func>
class Promise;

template <typename Ret, typename... Args>
class Promise<Ret(Args...)> : public PromiseBase<Args...> {
public:
  template <typename Func>
  explicit Promise(Func func)
    : handler{func} {}

  template <typename Func>
  auto &then(Func func) {
    assert(!next);
    if constexpr (std::is_void_v<Ret>) {
      using NextSig = std::invoke_result_t<Func>();
      auto nextPromise = std::make_unique<Promise<NextSig>>(func);
      auto &ret = *nextPromise.get();
      next = std::move(nextPromise);
      return ret; 
    } else {
      using NextSig = std::invoke_result_t<Func, Ret>(Ret);
      auto nextPromise = std::make_unique<Promise<NextSig>>(func);
      auto &ret = *nextPromise.get();
      next = std::move(nextPromise);
      return ret;
    }
  }

  void launch(Args... args) override {
    if (next) {
      if constexpr (std::is_void_v<Ret>) {
        handler(args...);
        next->launch();
      } else {
        next->launch(handler(args...));
      }
    } else {
      handler(args...);
    }
  }

private:
  using NextPromise = std::conditional_t<
    std::is_void_v<Ret>,
    PromiseBase<>,
    PromiseBase<Ret>
  >;
  std::unique_ptr<NextPromise> next;
  std::function<Ret(Args...)> handler;
};

Наконец, у нас есть руководство по выводам.

template <typename Func>
Promise(Func) -> Promise<signature_t<Func>>;

Вот онлайн демо .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...