cancel: Add utility for always running callback
This makes it possible to have a guaranteed callback, where an uncalled
callback will be called with a set of default parameters upon
destruction if it was not called at least once.
Change-Id: Id5b499e645adc9e23bf03158f10710f281bd7fd3
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/stdplus/cancel.hpp b/src/stdplus/cancel.hpp
index a6bf05b..e151e00 100644
--- a/src/stdplus/cancel.hpp
+++ b/src/stdplus/cancel.hpp
@@ -1,6 +1,9 @@
#pragma once
#include <stdplus/handle/managed.hpp>
+#include <tuple>
+#include <utility>
+
namespace stdplus
{
@@ -18,4 +21,98 @@
};
using Cancel = stdplus::Managed<Cancelable*>::HandleF<CancelableF>;
+namespace detail
+{
+
+struct fAny
+{
+};
+struct fPtr : fAny
+{
+};
+
+template <typename>
+struct validator
+{
+ typedef int type;
+};
+
+template <typename F,
+ typename validator<decltype(std::declval<F>() == nullptr)>::type = 0>
+inline bool fPop(const F& f, fPtr)
+{
+ return !(f == nullptr);
+}
+
+template <typename F>
+inline bool fPop(const F&, fAny)
+{
+ return true;
+}
+
+template <typename F>
+inline bool fPop(const F& f)
+{
+ return fPop(f, fPtr());
+}
+
+} // namespace detail
+
+template <typename F, typename... DefaultArgs>
+class AlwaysCallOnce
+{
+ public:
+ template <typename Fi, typename... Vs>
+ explicit AlwaysCallOnce(Fi&& fi, Vs&&... default_args) :
+ f(std::forward<Fi>(fi)), default_args(std::forward<Vs>(default_args)...)
+ {
+ }
+ AlwaysCallOnce(const AlwaysCallOnce&) = delete;
+ AlwaysCallOnce(AlwaysCallOnce&& other) noexcept :
+ f(std::move(other.f)), default_args(std::move(other.default_args)),
+ called(std::exchange(other.called, true))
+ {
+ }
+ AlwaysCallOnce& operator=(const AlwaysCallOnce&) = delete;
+ AlwaysCallOnce& operator=(AlwaysCallOnce&& other) noexcept
+ {
+ finalCall();
+ f = std::move(other.f);
+ default_args = std::move(other.default_args);
+ called = std::exchange(other.called, true);
+ return *this;
+ }
+ ~AlwaysCallOnce()
+ {
+ finalCall();
+ }
+
+ template <typename... Args>
+ auto operator()(Args&&... args) noexcept
+ {
+ called = true;
+ return f(std::forward<Args>(args)...);
+ }
+
+ private:
+ F f;
+ std::tuple<DefaultArgs...> default_args;
+ bool called = false;
+
+ void finalCall() noexcept
+ {
+ if (!called && detail::fPop(f))
+ {
+ std::apply(f, default_args);
+ }
+ }
+};
+
+template <typename... Args>
+inline auto alwaysCallOnce(Args&&... args)
+{
+ return AlwaysCallOnce<std::remove_cv_t<std::remove_reference_t<Args>>...>(
+ std::forward<Args>(args)...);
+}
+
} // namespace stdplus
diff --git a/test/cancel.cpp b/test/cancel.cpp
index 82da693..d8c75a8 100644
--- a/test/cancel.cpp
+++ b/test/cancel.cpp
@@ -1,3 +1,4 @@
+#include <functional>
#include <stdplus/cancel.hpp>
#include <gtest/gtest.h>
@@ -25,4 +26,37 @@
EXPECT_EQ(c.count, 1);
}
+TEST(CancelTest, AlwaysCallOnceLambda)
+{
+ size_t ctr = 0;
+ {
+ auto i = std::make_unique<size_t>(1);
+ auto acb = alwaysCallOnce([&, i = std::move(i)]() { ctr += *i; });
+ EXPECT_EQ(ctr, 0);
+ acb();
+ EXPECT_EQ(ctr, 1);
+ }
+ EXPECT_EQ(ctr, 1);
+ {
+ auto acb = alwaysCallOnce([&](size_t i) { ctr += i; }, 3);
+ EXPECT_EQ(ctr, 1);
+ }
+ EXPECT_EQ(ctr, 4);
+}
+
+TEST(CanceTest, AlwaysCallOnceFunction)
+{
+ size_t ctr = 0;
+ {
+ auto acb = alwaysCallOnce(std::function<void()>(nullptr));
+ }
+ {
+ auto acb = alwaysCallOnce(std::function<void()>([&]() { ctr++; }));
+ EXPECT_EQ(ctr, 0);
+ acb = alwaysCallOnce(std::function<void()>([&]() { ctr += 2; }));
+ EXPECT_EQ(ctr, 1);
+ }
+ EXPECT_EQ(ctr, 3);
+}
+
} // namespace stdplus