cancel: Add cancelable utility

This adds a common paradigm for handling a task that may need to be
canceled.

Change-Id: I2d06318f56788045f2688da137698dd7177e1fa2
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/meson.build b/src/meson.build
index dc4e02f..11bebe7 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -96,6 +96,7 @@
   requires: stdplus_reqs)
 
 install_headers(
+  'stdplus/cancel.hpp',
   'stdplus/exception.hpp',
   'stdplus/flags.hpp',
   'stdplus/raw.hpp',
diff --git a/src/stdplus/cancel.hpp b/src/stdplus/cancel.hpp
new file mode 100644
index 0000000..a6bf05b
--- /dev/null
+++ b/src/stdplus/cancel.hpp
@@ -0,0 +1,21 @@
+#pragma once
+#include <stdplus/handle/managed.hpp>
+
+namespace stdplus
+{
+
+struct Cancelable
+{
+    virtual ~Cancelable() = default;
+    virtual void cancel() noexcept = 0;
+};
+struct CancelableF
+{
+    inline void operator()(Cancelable* c) noexcept
+    {
+        c->cancel();
+    }
+};
+using Cancel = stdplus::Managed<Cancelable*>::HandleF<CancelableF>;
+
+} // namespace stdplus
diff --git a/test/cancel.cpp b/test/cancel.cpp
new file mode 100644
index 0000000..82da693
--- /dev/null
+++ b/test/cancel.cpp
@@ -0,0 +1,28 @@
+#include <stdplus/cancel.hpp>
+
+#include <gtest/gtest.h>
+
+namespace stdplus
+{
+
+struct FakeCancelable : public Cancelable
+{
+    size_t count = 0;
+    void cancel() noexcept override
+    {
+        count++;
+    }
+};
+
+TEST(CancelTest, Cancel)
+{
+    FakeCancelable c;
+    EXPECT_EQ(c.count, 0);
+    {
+        Cancel cancel(&c);
+        EXPECT_EQ(c.count, 0);
+    }
+    EXPECT_EQ(c.count, 1);
+}
+
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index cd89f27..8a2ae6c 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -21,6 +21,7 @@
 endif
 
 gtests = [
+  'cancel',
   'exception',
   'handle/copyable',
   'handle/managed',