Skip to content

Instantly share code, notes, and snippets.

@RedBeard0531
Last active April 26, 2018 15:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RedBeard0531/f2466679b3acd06cd79d06e5cf29fda6 to your computer and use it in GitHub Desktop.
Save RedBeard0531/f2466679b3acd06cd79d06e5cf29fda6 to your computer and use it in GitHub Desktop.
Future then() traits
#include <tuple>
#include <optional>
#include <functional>
/////////////////////
/// PRIMARY TEMPLATES
template <typename T>
struct future_then_return_traits {
// Future::then(cb) for a callback that returns T,
// returns Future<future_then_return_traits<T>::result_type>
using result_type = T;
// This is called to complete the output future after a then() callback returns T.
// result is the return value from the callback.
// promise is associated with the returned Future<result_type<T>.
// We can easily replace the promise with some other completion API.
template <typename Promise>
static void adapt(Promise&& p, T&& result) {
p.set_value(std::move(result));
}
};
template <typename T>
struct future_invocation_traits {
// This is called when a Future<T> is ready to invoke a callback.
// It allows a type to customize how it is passed to the callback.
// result is a ready future.
template <typename Callback, typename Future>
static auto invoke(Callback&& cb, Future&& result) {
// Also need error propagation. Exercise for reader.
return std::invoke(std::forward<Callback>(cb), std::move(result).get());
}
};
//////////////////////////////////////////////////////////////
/// Specializations for a concrete future type with unwrapping
template <typename T>
struct future_t;
template <typename T>
struct future_then_return_traits<future_t<T>> {
using result_type = T;
template <typename Promise>
static void adapt(Promise&& p, future_t<T>&& result) {
// Also need error propagation. Exercise for reader.
std::move(result).then([p = std::forward<Promise>(p)] (T&& v) {
p.set_value(std::move(v));
});
}
};
// Uses default invocation traits.
///////////////////////////////
/// Specializations for tuple
// Uses default return traits.
template <typename... Ts>
struct future_invocation_traits<std::tuple<Ts...>> {
template <typename Callback, typename Future>
static auto invoke(Callback&& cb, Future&& result) {
// Also need error propagation. Exercise for reader.
return std::apply(std::forward<Callback>(cb), std::move(result).get());
}
};
///////////////////////////////
/// Specializations for void
// Uses default return traits.
template<>
struct future_invocation_traits<void> {
template <typename Callback, typename Future>
static auto invoke(Callback&& cb, Future&& result) {
// Also need error propagation. Exercise for reader.
return std::invoke(std::forward<Callback>(cb));
}
};
///////////////////////////////////////////
/// Specializations for a Try/expected type
template <typename T>
struct expected {
std::optional<T> val;
std::exception_ptr err;
};
template <typename T>
struct future_then_return_traits<expected<T>> {
using result_type = T;
template <typename Promise>
static void adapt(Promise&& p, expected<T>&& result) {
if (result.val) {
p.set_value(std::move(*result.val));
} else {
p.set_error(std::move(result.err));
}
}
};
//////////////////////////////////////////////////////////////////////////
/// Specializations for a "keep" type that suppresses all special behavior
template <typename T>
struct keep {
T val;
};
template <typename T>
struct future_then_return_traits<keep<T>> {
using result_type = T;
template <typename Promise>
static void adapt(Promise&& p, keep<T>&& result) {
p.set_value(std::move(result.val));
}
};
template <typename T>
struct future_invocation_traits<keep<T>> {
template <typename Callback, typename Future>
static auto invoke(Callback&& cb, Future&& result) {
// Also need error propagation. Exercise for reader.
return std::invoke(std::forward<Callback>(cb), std::move(result).get().val);
}
};
//////////////////////////////
/// Example using these traits
template <typename T>
struct promise_t;
template <typename T>
struct future_t {
template <
// Callers can (but don't have to) explicitly specify the traits to use
// to customize how arguments will be passed to their callback.
template<typename> typename ArgTraits = future_invocation_traits,
typename Callback = void, // This needs to be deduced but can't be first.
typename RawResult = decltype(ArgTraits<T>::invoke(std::declval<Callback>(),
std::declval<future_t>())),
typename Result = typename future_then_return_traits<RawResult>::result_type
>
future_t<Result> then(Callback&& cb) {
promise_t<Result> p;
auto ret = p.get_future();
onCompletion([cb = std::forward<Callback>(cb), p = std::move(p)] (future_t&& readyFut) {
future_then_return_traits<T>(
std::move(p),
ArgTraits<T>::invoke(cb, std::move(readyFut)));
});
return ret;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment