У меня есть кое-что, что я думаю, делает то, что вы хотите. Я начну с примера, а затем представлю реализацию.
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>>;
Вот онлайн демо .