handle/copyable: Implement 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 copied or destroyed.

Tested:
    Built and run through unit tests.

Change-Id: I3d23544b2e7c8d8c6686effc03b3b7433ea18bf5
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/.gitignore b/.gitignore
index 4524a76..f58f9f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,5 +41,6 @@
 /src/stdplus.pc
 
 # Output binaries
+/test/handle/copyable
 /test/handle/managed
 /test/signal
diff --git a/src/Makefile.am b/src/Makefile.am
index 57af82a..d45007e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,6 +4,8 @@
 libstdplus_la_SOURCES =
 libstdplus_la_LIBADD = $(COMMON_LIBS)
 
+nobase_include_HEADERS += stdplus/handle/copyable.hpp
+
 nobase_include_HEADERS += stdplus/handle/managed.hpp
 
 nobase_include_HEADERS += stdplus/signal.hpp
diff --git a/src/meson.build b/src/meson.build
index 6b1fea9..a6ff714 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -20,5 +20,6 @@
   subdir: 'stdplus')
 
 install_headers(
+  'stdplus/handle/copyable.hpp',
   'stdplus/handle/managed.hpp',
   subdir: 'stdplus/handle')
diff --git a/src/stdplus/handle/copyable.hpp b/src/stdplus/handle/copyable.hpp
new file mode 100644
index 0000000..df383db
--- /dev/null
+++ b/src/stdplus/handle/copyable.hpp
@@ -0,0 +1,112 @@
+#pragma once
+#include <optional>
+#include <stdplus/handle/managed.hpp>
+#include <utility>
+
+namespace stdplus
+{
+
+/** @brief Similar to the Managed Handle, but also allows for copying
+ *         and performs an operation during that copy.
+ */
+template <typename T, typename... As>
+struct Copyable
+{
+    template <void (*drop)(T&&, As&...), T (*ref)(const T&, As&...)>
+    class Handle : public Managed<T, As...>::template Handle<drop>
+    {
+      public:
+        using MHandle = typename Managed<T, As...>::template Handle<drop>;
+
+        /** @brief Creates a handle referencing the object
+         *
+         *  @param[in] maybeV - Optional object being managed
+         */
+        template <typename... Vs>
+        constexpr explicit Handle(const std::optional<T>& maybeV, Vs&&... vs) :
+            MHandle(std::nullopt, std::forward<Vs>(vs)...)
+        {
+            reset(maybeV);
+        }
+        template <typename... Vs>
+        constexpr explicit Handle(const T& maybeV, Vs&&... vs) :
+            MHandle(std::nullopt, std::forward<Vs>(vs)...)
+        {
+            reset(maybeV);
+        }
+
+        /** @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 :
+            MHandle(std::move(maybeV), std::forward<Vs>(vs)...)
+        {
+        }
+        template <typename... Vs>
+        constexpr explicit Handle(T&& maybeV, Vs&&... vs) noexcept :
+            MHandle(std::move(maybeV), std::forward<Vs>(vs)...)
+        {
+        }
+
+        constexpr Handle(const Handle& other) : MHandle(std::nullopt, other.as)
+        {
+            reset(other.maybe_value());
+        }
+
+        constexpr Handle(Handle&& other) noexcept : MHandle(std::move(other))
+        {
+        }
+
+        constexpr Handle& operator=(const Handle& other)
+        {
+            if (this != &other)
+            {
+                reset();
+                this->as = other.as;
+                reset(other.maybe_value());
+            }
+            return *this;
+        }
+
+        constexpr Handle& operator=(Handle&& other) noexcept
+        {
+            MHandle::operator=(std::move(other));
+            return *this;
+        }
+
+        using MHandle::reset;
+
+        /** @brief Resets the managed value to a new value
+         *         Takes a new reference on the value
+         *
+         *  @param[in] maybeV - Maybe the new value
+         */
+        constexpr void reset(const std::optional<T>& maybeV)
+        {
+            if (maybeV)
+            {
+                reset(doRef(*maybeV, std::index_sequence_for<As...>()));
+            }
+            else
+            {
+                reset(std::nullopt);
+            }
+        }
+        constexpr void reset(const T& maybeV)
+        {
+            reset(doRef(maybeV, std::index_sequence_for<As...>()));
+        }
+
+      private:
+        template <size_t... Indices>
+        T doRef(const T& v, std::index_sequence<Indices...>)
+        {
+            return ref(v, std::get<Indices>(this->as)...);
+        }
+    };
+};
+
+} // namespace stdplus
diff --git a/test/Makefile.am b/test/Makefile.am
index 45f54f4..76cd56f 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -7,6 +7,11 @@
 check_PROGRAMS =
 TESTS = $(check_PROGRAMS)
 
+check_PROGRAMS += handle/copyable
+handle_copyable_SOURCES = handle/copyable.cpp
+handle_copyable_CPPFLAGS = $(gtest_cppflags)
+handle_copyable_LDADD = $(gtest_ldadd)
+
 check_PROGRAMS += handle/managed
 handle_managed_SOURCES = handle/managed.cpp
 handle_managed_CPPFLAGS = $(gtest_cppflags)
diff --git a/test/handle/copyable.cpp b/test/handle/copyable.cpp
new file mode 100644
index 0000000..6ce210f
--- /dev/null
+++ b/test/handle/copyable.cpp
@@ -0,0 +1,347 @@
+#include <gtest/gtest.h>
+#include <optional>
+#include <stdplus/handle/copyable.hpp>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace stdplus
+{
+namespace
+{
+
+static std::vector<int> reffed;
+static int stored_ref = 0;
+static std::vector<int> dropped;
+static int stored_drop = 0;
+
+int ref(const int& i)
+{
+    reffed.push_back(i);
+    return i + 1;
+}
+
+void drop(int&& i)
+{
+    dropped.push_back(std::move(i));
+}
+
+int ref(const int& i, std::string&, int& si)
+{
+    reffed.push_back(i);
+    // Make sure we can update the stored data
+    stored_ref = si++;
+    return i + 1;
+}
+
+void drop(int&& i, std::string&, int& si)
+{
+    dropped.push_back(std::move(i));
+    // Make sure we can update the stored data
+    stored_drop = si++;
+}
+
+using SimpleHandle = Copyable<int>::Handle<drop, ref>;
+using StoreHandle = Copyable<int, std::string, int>::Handle<drop, ref>;
+
+class CopyableHandleTest : public ::testing::Test
+{
+  protected:
+    void SetUp()
+    {
+        reffed.clear();
+        dropped.clear();
+    }
+
+    void TearDown()
+    {
+        EXPECT_TRUE(reffed.empty());
+        EXPECT_TRUE(dropped.empty());
+    }
+};
+
+TEST_F(CopyableHandleTest, EmptyNoStorage)
+{
+    SimpleHandle h(std::nullopt);
+    EXPECT_FALSE(h);
+    EXPECT_THROW(h.value(), std::bad_optional_access);
+    h.reset();
+    EXPECT_FALSE(h);
+    EXPECT_THROW(h.value(), std::bad_optional_access);
+}
+
+TEST_F(CopyableHandleTest, EmptyWithStorage)
+{
+    auto maybeV = std::nullopt;
+    StoreHandle h(maybeV, "str", 5);
+    EXPECT_FALSE(h);
+    EXPECT_THROW(h.value(), std::bad_optional_access);
+    h.reset(maybeV);
+    EXPECT_FALSE(h);
+    EXPECT_THROW(h.value(), std::bad_optional_access);
+}
+
+TEST_F(CopyableHandleTest, SimplePopulated)
+{
+    constexpr int expected = 3;
+    {
+        int val = expected;
+        SimpleHandle h(std::move(val));
+        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(CopyableHandleTest, 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_TRUE(reffed.empty());
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+    {
+        const std::optional<int> maybeVal{expected};
+        SimpleHandle h(maybeVal);
+        EXPECT_TRUE(h);
+        EXPECT_EQ(expected + 1, *h);
+        EXPECT_EQ(expected + 1, h.value());
+        EXPECT_EQ(std::vector{expected}, reffed);
+        reffed.clear();
+        EXPECT_TRUE(dropped.empty());
+    }
+    EXPECT_EQ(std::vector{expected + 1}, dropped);
+    dropped.clear();
+}
+
+TEST_F(CopyableHandleTest, SimplePopulatedWithStorage)
+{
+    constexpr int expected = 3;
+    {
+        StoreHandle h(expected, std::string{"str"}, 5);
+        EXPECT_TRUE(h);
+        EXPECT_EQ(expected + 1, *h);
+        EXPECT_EQ(expected + 1, h.value());
+        EXPECT_EQ(5, stored_ref);
+        EXPECT_EQ(std::vector{expected}, reffed);
+        reffed.clear();
+        EXPECT_TRUE(dropped.empty());
+    }
+    EXPECT_EQ(6, stored_drop);
+    EXPECT_EQ(std::vector{expected + 1}, dropped);
+    dropped.clear();
+}
+
+TEST_F(CopyableHandleTest, 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_drop);
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+}
+
+TEST_F(CopyableHandleTest, 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(CopyableHandleTest, ResetCopyPopulated)
+{
+    constexpr int expected = 3, expected2 = 10;
+    {
+        SimpleHandle h(int{expected});
+        EXPECT_TRUE(reffed.empty());
+        EXPECT_TRUE(dropped.empty());
+        const std::optional<int> maybe2{expected2};
+        h.reset(maybe2);
+        EXPECT_TRUE(h);
+        EXPECT_EQ(expected2 + 1, *h);
+        EXPECT_EQ(expected2 + 1, h.value());
+        EXPECT_EQ(std::vector{expected2}, reffed);
+        reffed.clear();
+        EXPECT_EQ(std::vector{expected}, dropped);
+        dropped.clear();
+    }
+    EXPECT_EQ(std::vector{expected2 + 1}, dropped);
+    dropped.clear();
+}
+
+TEST_F(CopyableHandleTest, ResetCopyPopulatedWithStorage)
+{
+    constexpr int expected = 3, expected2 = 10;
+    {
+        StoreHandle h(int{expected}, "str", 5);
+        EXPECT_TRUE(dropped.empty());
+        h.reset(expected2);
+        EXPECT_TRUE(h);
+        EXPECT_EQ(expected2 + 1, *h);
+        EXPECT_EQ(expected2 + 1, h.value());
+        EXPECT_EQ(5, stored_ref);
+        EXPECT_EQ(std::vector{expected2}, reffed);
+        reffed.clear();
+        EXPECT_EQ(6, stored_drop);
+        EXPECT_EQ(std::vector{expected}, dropped);
+        dropped.clear();
+    }
+    EXPECT_EQ(7, stored_drop);
+    EXPECT_EQ(std::vector{expected2 + 1}, dropped);
+    dropped.clear();
+}
+
+TEST_F(CopyableHandleTest, 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_drop);
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+}
+
+TEST_F(CopyableHandleTest, 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_drop);
+        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_drop);
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+}
+
+TEST_F(CopyableHandleTest, CopyConstructSrcEmptyWithStorage)
+{
+    StoreHandle h1(std::nullopt, "str", 5);
+    StoreHandle h2(h1);
+}
+
+TEST_F(CopyableHandleTest, CopyConstructWithStorage)
+{
+    constexpr int expected = 3;
+    StoreHandle h1(int{expected}, "str", 5);
+    StoreHandle h2(h1);
+    EXPECT_EQ(5, stored_ref);
+    EXPECT_EQ(std::vector{expected}, reffed);
+    reffed.clear();
+
+    h1.reset();
+    EXPECT_EQ(5, stored_drop);
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+    h2.reset();
+    EXPECT_EQ(6, stored_drop);
+    EXPECT_EQ(std::vector{expected + 1}, dropped);
+    dropped.clear();
+}
+
+TEST_F(CopyableHandleTest, CopyAssignBothEmptyWithStorage)
+{
+    StoreHandle h1(std::nullopt, "str", 5);
+    StoreHandle h2(std::nullopt, "str", 10);
+    h2 = h1;
+}
+
+TEST_F(CopyableHandleTest, CopyAssignSrcEmptyWithStorage)
+{
+    constexpr int expected = 3;
+    StoreHandle h1(std::nullopt, "str", 5);
+    StoreHandle h2(int{expected}, "str", 10);
+    h2 = h1;
+    EXPECT_EQ(10, stored_drop);
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+}
+
+TEST_F(CopyableHandleTest, CopyAssignDstEmptyWithStorage)
+{
+    constexpr int expected = 3;
+    StoreHandle h1(int{expected}, "str", 5);
+    StoreHandle h2(std::nullopt, "str", 10);
+    h2 = h1;
+    EXPECT_EQ(5, stored_ref);
+    EXPECT_EQ(std::vector{expected}, reffed);
+    reffed.clear();
+
+    h1.reset();
+    EXPECT_EQ(5, stored_drop);
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+    h2.reset();
+    EXPECT_EQ(6, stored_drop);
+    EXPECT_EQ(std::vector{expected + 1}, dropped);
+    dropped.clear();
+}
+
+TEST_F(CopyableHandleTest, CopyAssignWithStorage)
+{
+    constexpr int expected = 3, expected2 = 15;
+    StoreHandle h1(int{expected}, "str", 5);
+    StoreHandle h2(int{expected2}, "str", 10);
+    h2 = h1;
+    EXPECT_EQ(10, stored_drop);
+    EXPECT_EQ(std::vector{expected2}, dropped);
+    dropped.clear();
+    EXPECT_EQ(5, stored_ref);
+    EXPECT_EQ(std::vector{expected}, reffed);
+    reffed.clear();
+
+    h1.reset();
+    EXPECT_EQ(5, stored_drop);
+    EXPECT_EQ(std::vector{expected}, dropped);
+    dropped.clear();
+    h2.reset();
+    EXPECT_EQ(6, stored_drop);
+    EXPECT_EQ(std::vector{expected + 1}, dropped);
+    dropped.clear();
+}
+
+} // namespace
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index 812de29..cc3d12d 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -3,6 +3,7 @@
 
 tests = [
   'signal',
+  'handle/copyable',
   'handle/managed',
 ]