variant: Add equals operator helper

This adds a generic equality operator intended to make it
straightforward to compare a variant to other variants or base values.

Change-Id: If59296e650bfb43880931e146e4e50b3d8aaa38f
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index 50ecdef..1402818 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -20,6 +20,7 @@
   'stdplus/str/maps.hpp',
   'stdplus/util/cexec.hpp',
   'stdplus/util/string.hpp',
+  'stdplus/variant.hpp',
   'stdplus/zstring.hpp',
   'stdplus/zstring_view.hpp',
   preserve_path: true)
diff --git a/include/stdplus/variant.hpp b/include/stdplus/variant.hpp
new file mode 100644
index 0000000..61d2e27
--- /dev/null
+++ b/include/stdplus/variant.hpp
@@ -0,0 +1,109 @@
+#include <type_traits>
+#include <utility>
+#include <variant>
+
+namespace stdplus
+{
+namespace detail
+{
+
+template <template <typename, typename, typename = void> typename Veq,
+          typename...>
+struct CanVeq;
+
+template <template <typename, typename, typename = void> typename Veq,
+          typename T>
+struct CanVeq<Veq, T>
+{
+    static constexpr inline bool value = false;
+};
+
+template <template <typename, typename, typename = void> typename Veq,
+          typename T, typename V, typename... Vs>
+struct CanVeq<Veq, T, V, Vs...>
+{
+    static constexpr inline bool value = Veq<T, V>::value ||
+                                         CanVeq<Veq, T, Vs...>::value;
+};
+
+template <typename T1, typename T2>
+struct VeqBase
+{
+    static constexpr inline bool value = false;
+
+    constexpr bool operator()(const T1&, const T2&) const noexcept
+    {
+        return false;
+    }
+};
+
+template <typename T1, typename T2, typename = void>
+struct VeqFuzzy : VeqBase<T1, T2>
+{};
+
+template <typename T1, typename T2>
+struct VeqFuzzy<T1, T2,
+                std::enable_if_t<std::is_same_v<
+                    decltype(std::declval<T1>() == std::declval<T2>()), bool>>>
+{
+    static constexpr inline bool value = true;
+
+    constexpr bool operator()(const T1& lhs, const T2& rhs) const noexcept
+    {
+        return lhs == rhs;
+    }
+};
+
+template <typename T1, typename T2, typename = void>
+struct VeqStrict : VeqBase<T1, T2>
+{};
+
+template <typename T1, typename T2>
+struct VeqStrict<T1, T2, std::enable_if_t<std::is_same_v<T1, T2>>>
+{
+    static constexpr inline bool value = true;
+
+    constexpr bool operator()(const T1& lhs, const T2& rhs) const noexcept
+    {
+        return lhs == rhs;
+    }
+};
+
+} // namespace detail
+
+template <template <typename, typename, typename = void> typename Veq,
+          typename... Vs, typename T,
+          std::enable_if_t<detail::CanVeq<Veq, T, Vs...>::value, bool> = true>
+constexpr bool variantEq(const std::variant<Vs...>& vs, const T& t) noexcept
+{
+    return std::visit(
+        [&t](const auto& v) {
+        return Veq<std::remove_cvref_t<decltype(v)>, T>{}(v, t);
+        },
+        vs);
+}
+
+template <template <typename, typename, typename = void> typename Veq,
+          typename... Vs, typename... Vs2>
+constexpr bool variantEq(const std::variant<Vs...>& vs,
+                         const std::variant<Vs2...>& vs2) noexcept
+{
+    return std::visit([&vs](const auto& v) { return variantEq<Veq>(vs, v); },
+                      vs2);
+}
+
+template <typename... Vs>
+constexpr bool variantEqFuzzy(const std::variant<Vs...>& vs,
+                              const auto& t) noexcept
+{
+    return variantEq<detail::VeqFuzzy>(vs, t);
+}
+
+template <typename... Vs>
+constexpr bool variantEqStrict(const std::variant<Vs...>& vs,
+                               const auto& t) noexcept
+{
+    return variantEq<detail::VeqStrict>(vs, t);
+}
+
+} // namespace stdplus
diff --git a/src/meson.build b/src/meson.build
index 7367761..dd67b35 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -60,6 +60,7 @@
   'str/cexpr.cpp',
   'str/maps.cpp',
   'util/cexec.cpp',
+  'variant.cpp',
   'zstring.cpp',
   'zstring_view.cpp',
 ]
diff --git a/src/variant.cpp b/src/variant.cpp
new file mode 100644
index 0000000..6af326c
--- /dev/null
+++ b/src/variant.cpp
@@ -0,0 +1 @@
+#include <stdplus/variant.hpp>
diff --git a/test/meson.build b/test/meson.build
index c9ee047..9c0580b 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -16,6 +16,7 @@
   'str/cexpr': [stdplus_dep, gtest_main_dep],
   'str/maps': [stdplus_dep, gmock_dep, gtest_main_dep],
   'util/cexec': [stdplus_dep, gtest_main_dep],
+  'variant': [stdplus_dep, gtest_main_dep],
   'zstring': [stdplus_dep, gtest_main_dep],
   'zstring_view': [stdplus_dep, gtest_main_dep],
 }
diff --git a/test/variant.cpp b/test/variant.cpp
new file mode 100644
index 0000000..48b4843
--- /dev/null
+++ b/test/variant.cpp
@@ -0,0 +1,41 @@
+#include <stdplus/variant.hpp>
+
+#include <gtest/gtest.h>
+
+namespace stdplus
+{
+
+using namespace std::literals::string_literals;
+using V = std::variant<int, float, std::string>;
+
+TEST(variantEqFuzzy, Basic)
+{
+    EXPECT_TRUE(variantEqFuzzy(V{1}, 1));
+    EXPECT_TRUE(variantEqFuzzy(V{1}, V{1}));
+    EXPECT_TRUE(variantEqFuzzy(V{1}, 1.));
+    EXPECT_TRUE(variantEqFuzzy(V{1}, V{1.f}));
+    EXPECT_TRUE(variantEqFuzzy(V{1.f}, 1));
+    EXPECT_TRUE(variantEqFuzzy(V{1.f}, 1.));
+    EXPECT_TRUE(variantEqFuzzy(V{1.f}, 1.f));
+    EXPECT_FALSE(variantEqFuzzy(V{"1"}, 1));
+    EXPECT_FALSE(variantEqFuzzy(V{"1"}, 1.));
+    EXPECT_TRUE(variantEqFuzzy(V{"1"}, "1"));
+    EXPECT_TRUE(variantEqFuzzy(V{"1"}, V{"1"}));
+    EXPECT_FALSE(variantEqFuzzy(V{"1"}, V{1}));
+}
+
+TEST(variantEqStrict, Basic)
+{
+    EXPECT_TRUE(variantEqStrict(V{1}, 1));
+    EXPECT_TRUE(variantEqStrict(V{1}, V{1}));
+    EXPECT_FALSE(variantEqStrict(V{1}, 1.f));
+    EXPECT_FALSE(variantEqStrict(V{1}, V{1.f}));
+    EXPECT_FALSE(variantEqStrict(V{1.f}, 1));
+    EXPECT_TRUE(variantEqStrict(V{1.f}, 1.f));
+    EXPECT_FALSE(variantEqStrict(V{"1"}, 1));
+    EXPECT_FALSE(variantEqStrict(V{"1"}, 1.f));
+    EXPECT_TRUE(variantEqStrict(V{"1"}, "1"s));
+    EXPECT_TRUE(variantEqStrict(V{"1"}, V{"1"}));
+}
+
+} // namespace stdplus