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