handle/managed: Implement non-copyable handle

This is a generic handle type that holds a resource and uses RAII to
call a user defined function when the resource is destroyed. A future
change will implement a smart file descriptor based on this interface.

A follow up change will implement the copyable version.

Tested:
    Built and run through unit tests.

Change-Id: Ia8da1d662319e8fb58380ed4979bcf1b74f66dfb
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/.gitignore b/.gitignore
index 205819a..4524a76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,4 +41,5 @@
 /src/stdplus.pc
 
 # Output binaries
+/test/handle/managed
 /test/signal
diff --git a/src/Makefile.am b/src/Makefile.am
index 5e935f4..57af82a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,5 +4,7 @@
 libstdplus_la_SOURCES =
 libstdplus_la_LIBADD = $(COMMON_LIBS)
 
+nobase_include_HEADERS += stdplus/handle/managed.hpp
+
 nobase_include_HEADERS += stdplus/signal.hpp
 libstdplus_la_SOURCES += stdplus/signal.cpp
diff --git a/src/meson.build b/src/meson.build
index fd56941..6b1fea9 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -18,3 +18,7 @@
 install_headers(
   'stdplus/signal.hpp',
   subdir: 'stdplus')
+
+install_headers(
+  'stdplus/handle/managed.hpp',
+  subdir: 'stdplus/handle')
diff --git a/src/stdplus/handle/managed.hpp b/src/stdplus/handle/managed.hpp
new file mode 100644
index 0000000..361c1ec
--- /dev/null
+++ b/src/stdplus/handle/managed.hpp
@@ -0,0 +1,183 @@
+#pragma once
+#include <cstdlib>
+#include <optional>
+#include <tuple>
+#include <utility>
+
+namespace stdplus
+{
+
+/** @brief   An RAII handle which takes an object and calls a user specified
+ *           function on the object when it should be cleaned up.
+ *  @details This is useful for adding RAII semantics to non-RAII things like
+ *           file descriptors, or c structs allocated with special routines.
+ *           We could make a simple file descriptor wrapper that is
+ *           automatically closed:
+ *
+ *           void closefd(int&& fd) { close(fd); }
+ *           using Fd = Managed<int>::Handle<closefd>;
+ *
+ *           void some_func()
+ *           {
+ *               Fd fd(open("somefile", 0));
+ *               char buf[4096];
+ *               int amt = read(*fd, buf, sizeof(buf));
+ *               printf("%.*s\n", amt, data);
+ *           }
+ */
+template <typename T, typename... As>
+struct Managed
+{
+    template <void (*drop)(T&&, As&...)>
+    class Handle
+    {
+      public:
+        /** @brief Creates a handle owning the object
+         *
+         *  @param[in] maybeV - Maybe the object being managed
+         */
+        template <typename... Vs>
+        constexpr explicit Handle(std::optional<T>&& maybeV,
+                                  Vs&&... vs) noexcept :
+            as(std::forward<Vs>(vs)...),
+            maybeT(std::move(maybeV))
+        {
+        }
+        template <typename... Vs>
+        constexpr explicit Handle(T&& maybeV, Vs&&... vs) noexcept :
+            as(std::forward<Vs>(vs)...), maybeT(std::move(maybeV))
+        {
+        }
+
+        Handle(const Handle& other) = delete;
+        Handle& operator=(const Handle& other) = delete;
+
+        constexpr Handle(Handle&& other) :
+            as(std::move(other.as)), maybeT(std::move(other.maybeT))
+        {
+            other.maybeT = std::nullopt;
+        }
+
+        constexpr Handle& operator=(Handle&& other)
+        {
+            if (this != &other)
+            {
+                reset(std::move(other.maybeT));
+                as = std::move(other.as);
+                other.maybeT = std::nullopt;
+            }
+            return *this;
+        }
+
+        virtual ~Handle()
+        {
+            try
+            {
+                reset();
+            }
+            catch (...)
+            {
+                std::abort();
+            }
+        }
+
+        /** @brief Gets the managed object
+         *
+         *  @return A pointer to the object
+         */
+        constexpr const T* operator->() const noexcept
+        {
+            return &(*maybeT);
+        }
+
+        /** @brief Gets the managed object
+         *
+         *  @return A reference to the object
+         */
+        constexpr const T& operator*() const& noexcept
+        {
+            return *maybeT;
+        }
+
+        /** @brief Determine if we are managing an object
+         *
+         *  @return Do we currently have an object
+         */
+        constexpr explicit operator bool() const noexcept
+        {
+            return static_cast<bool>(maybeT);
+        }
+
+        /** @brief Determine if we are managing an object
+         *
+         *  @return Do we currently have an object
+         */
+        constexpr bool has_value() const noexcept
+        {
+            return maybeT.has_value();
+        }
+
+        /** @brief Gets the managed object
+         *
+         *  @throws std::bad_optional_access if it has no object
+         *  @return A reference to the managed object
+         */
+        constexpr const T& value() const&
+        {
+            return maybeT.value();
+        }
+
+        /** @brief Gets the managed object if it exists
+         *
+         *  @throws std::bad_optional_access if it has no object
+         *  @return A reference to the managed object
+         */
+        constexpr const std::optional<T>& maybe_value() const& noexcept
+        {
+            return maybeT;
+        }
+
+        /** @brief Resets the managed value to a new value
+         *         The container takes ownership of the value
+         *
+         *  @param[in] maybeV - Maybe the new value
+         */
+        constexpr void reset(std::optional<T>&& maybeV)
+        {
+            maybeDrop(std::index_sequence_for<As...>());
+            maybeT = std::move(maybeV);
+        }
+        constexpr void reset(T&& maybeV)
+        {
+            maybeDrop(std::index_sequence_for<As...>());
+            maybeT = std::move(maybeV);
+        }
+
+        /** @brief A shorthand reset function for convenience
+         *         Same as calling reset(std::nullopt)
+         */
+        constexpr void reset()
+        {
+            reset(std::nullopt);
+        }
+
+      protected:
+        /* Hold the data parameterized for this container */
+        std::tuple<As...> as;
+
+      private:
+        /* Stores the managed object if we have one */
+        std::optional<T> maybeT;
+
+        template <size_t... Indices>
+        void maybeDrop(std::index_sequence<Indices...>)
+        {
+            if (maybeT)
+            {
+                drop(std::move(*maybeT), std::get<Indices>(as)...);
+            }
+        }
+    };
+};
+
+} // namespace stdplus
diff --git a/test/Makefile.am b/test/Makefile.am
index 1c4a5a1..45f54f4 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -7,6 +7,11 @@
 check_PROGRAMS =
 TESTS = $(check_PROGRAMS)
 
+check_PROGRAMS += handle/managed
+handle_managed_SOURCES = handle/managed.cpp
+handle_managed_CPPFLAGS = $(gtest_cppflags)
+handle_managed_LDADD = $(gtest_ldadd)
+
 check_PROGRAMS += signal
 signal_SOURCES = signal.cpp
 signal_CPPFLAGS = $(gtest_cppflags)
diff --git a/test/handle/managed.cpp b/test/handle/managed.cpp
new file mode 100644
index 0000000..b3ba6a6
--- /dev/null
+++ b/test/handle/managed.cpp
@@ -0,0 +1,209 @@
+#include <gtest/gtest.h>
+#include <optional>
+#include <stdplus/handle/managed.hpp>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace stdplus
+{
+namespace
+{
+
+static std::vector<int> dropped;
+static int stored = 0;
+
+void drop(int&& i)
+{
+    dropped.push_back(std::move(i));
+}
+
+void drop(int&& i, std::string&, int& si)
+{
+    dropped.push_back(std::move(i));
+    // Make sure we can update the stored data
+    stored = si++;
+}
+
+using SimpleHandle = Managed<int>::Handle<drop>;
+using StoreHandle = Managed<int, std::string, int>::Handle<drop>;
+
+class ManagedHandleTest : public ::testing::Test
+{
+  protected:
+    void SetUp()
+    {
+        dropped.clear();
+    }
+
+    void TearDown()
+    {
+        EXPECT_TRUE(dropped.empty());
+    }
+};
+
+TEST_F(ManagedHandleTest, EmptyNoStorage)
+{
+    SimpleHandle h(std::nullopt);
+    EXPECT_FALSE(h);
+    EXPECT_THROW(h.value(), std::bad_optional_access);
+    h.reset();
+    EXPECT_FALSE(h.has_value());
+    EXPECT_THROW(h.value(), std::bad_optional_access);
+    EXPECT_EQ(std::nullopt, h.maybe_value());
+}
+
+TEST_F(ManagedHandleTest, EmptyWithStorage)
+{
+    StoreHandle h(std::nullopt, "str", 5);
+    EXPECT_FALSE(h);
+    EXPECT_THROW(h.value(), std::bad_optional_access);
+    h.reset(std::nullopt);
+    EXPECT_FALSE(h);
+    EXPECT_THROW(h.value(), std::bad_optional_access);
+
+    StoreHandle h2(std::nullopt, std::make_tuple<std::string, int>("str", 5));
+    EXPECT_FALSE(h2);
+    EXPECT_THROW(h2.value(), std::bad_optional_access);
+}
+
+TEST_F(ManagedHandleTest, SimplePopulated)
+{
+    constexpr int expected = 3;
+    {
+        int val = expected;
+        SimpleHandle h(std::move(val));
+        EXPECT_TRUE(h.has_value());
+        EXPECT_EQ(expected, *h);
+        EXPECT_EQ(expected, h.value());
+        EXPECT_EQ(expected, h.maybe_value());
+        EXPECT_TRUE(dropped.empty());
+    }
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+}
+
+TEST_F(ManagedHandleTest, OptionalPopulated)
+{
+    constexpr int expected = 3;
+    {
+        std::optional<int> maybeVal{expected};
+        SimpleHandle h(std::move(maybeVal));
+        EXPECT_TRUE(h);
+        EXPECT_EQ(expected, *h);
+        EXPECT_EQ(expected, h.value());
+        EXPECT_TRUE(dropped.empty());
+    }
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+}
+
+TEST_F(ManagedHandleTest, SimplePopulatedWithStorage)
+{
+    constexpr int expected = 3;
+    {
+        StoreHandle h(int{expected}, std::make_tuple(std::string{"str"}, 5));
+        EXPECT_TRUE(h);
+        EXPECT_EQ(expected, *h);
+        EXPECT_EQ(expected, h.value());
+        EXPECT_TRUE(dropped.empty());
+    }
+    EXPECT_EQ(5, stored);
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+}
+
+TEST_F(ManagedHandleTest, ResetPopulatedWithStorage)
+{
+    constexpr int expected = 3;
+    const std::string s{"str"};
+    StoreHandle h(int{expected}, s, 5);
+    EXPECT_TRUE(dropped.empty());
+    h.reset(std::nullopt);
+    EXPECT_FALSE(h);
+    EXPECT_THROW(h.value(), std::bad_optional_access);
+    EXPECT_EQ(5, stored);
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+}
+
+TEST_F(ManagedHandleTest, ResetNewPopulated)
+{
+    constexpr int expected = 3, expected2 = 10;
+    {
+        SimpleHandle h(int{expected});
+        EXPECT_TRUE(dropped.empty());
+        h.reset(int{expected2});
+        EXPECT_TRUE(h);
+        EXPECT_EQ(expected2, *h);
+        EXPECT_EQ(expected2, h.value());
+        EXPECT_EQ(std::vector{expected}, dropped);
+        dropped.clear();
+    }
+    EXPECT_EQ(std::vector{expected2}, dropped);
+    dropped.clear();
+}
+
+TEST_F(ManagedHandleTest, ResetNewPopulatedWithStorage)
+{
+    constexpr int expected = 3, expected2 = 10;
+    {
+        StoreHandle h(int{expected}, "str", 5);
+        EXPECT_TRUE(dropped.empty());
+        h.reset(int{expected2});
+        EXPECT_TRUE(h);
+        EXPECT_EQ(expected2, *h);
+        EXPECT_EQ(expected2, h.value());
+        EXPECT_EQ(5, stored);
+        EXPECT_EQ(std::vector{expected}, dropped);
+        dropped.clear();
+    }
+    EXPECT_EQ(6, stored);
+    EXPECT_EQ(std::vector{expected2}, dropped);
+    dropped.clear();
+}
+
+TEST_F(ManagedHandleTest, MoveConstructWithStorage)
+{
+    constexpr int expected = 3;
+    StoreHandle h1(int{expected}, "str", 5);
+    {
+        StoreHandle h2(std::move(h1));
+        EXPECT_TRUE(dropped.empty());
+        EXPECT_FALSE(h1);
+        EXPECT_THROW(h1.value(), std::bad_optional_access);
+        EXPECT_TRUE(h2);
+        EXPECT_EQ(expected, *h2);
+        EXPECT_EQ(expected, h2.value());
+    }
+    EXPECT_EQ(5, stored);
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+}
+
+TEST_F(ManagedHandleTest, MoveAssignWithStorage)
+{
+    constexpr int expected = 3, expected2 = 10;
+    {
+        StoreHandle h1(int{expected}, "str", 5);
+        StoreHandle h2(int{expected2}, "str", 10);
+        EXPECT_TRUE(dropped.empty());
+
+        h2 = std::move(h1);
+        EXPECT_EQ(10, stored);
+        EXPECT_EQ(std::vector{expected2}, dropped);
+        dropped.clear();
+        EXPECT_FALSE(h1);
+        EXPECT_THROW(h1.value(), std::bad_optional_access);
+        EXPECT_TRUE(h2);
+        EXPECT_EQ(expected, *h2);
+        EXPECT_EQ(expected, h2.value());
+    }
+    EXPECT_EQ(5, stored);
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+}
+
+} // namespace
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index b918d5d..812de29 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -3,6 +3,7 @@
 
 tests = [
   'signal',
+  'handle/managed',
 ]
 
 foreach t : tests