net/addr/subnet: Add string literals

Change-Id: Ibb6f97c3aa9b3fd7ef5c0c81e527741e7d26590f
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/stdplus/net/addr/subnet.hpp b/include/stdplus/net/addr/subnet.hpp
index 05464e6..2a91ed8 100644
--- a/include/stdplus/net/addr/subnet.hpp
+++ b/include/stdplus/net/addr/subnet.hpp
@@ -270,6 +270,87 @@
     }
 };
 
+namespace detail
+{
+
+template <Subnet Sub>
+struct CompileInAddrInt<Sub> : CompileInAddrInt<typename Sub::Addr>
+{
+    Sub::Pfx pfx = 0;
+
+    template <typename CharT>
+    constexpr void compile(std::basic_string_view<CharT> sv) noexcept
+    {
+        const auto pos = sv.rfind('/');
+        if (pos == sv.npos)
+        {
+            this->valid = false;
+        }
+        static_cast<CompileInAddrInt<typename Sub::Addr>&>(*this).compile(
+            sv.substr(0, pos));
+        try
+        {
+            pfx = StrToInt<10, typename Sub::Pfx>{}(sv.substr(pos + 1));
+        }
+        catch (...)
+        {
+            this->valid = false;
+        }
+    }
+};
+
+template <typename CharT, std::size_t N>
+struct CompileSubnet4 : CompileInAddr<Subnet4, CharT, N>
+{
+    constexpr CompileSubnet4(const CharT (&str)[N]) noexcept :
+        CompileInAddr<Subnet4, CharT, N>(str)
+    {}
+};
+
+template <typename CharT, std::size_t N>
+struct CompileSubnet6 : CompileInAddr<Subnet6, CharT, N>
+{
+    constexpr CompileSubnet6(const CharT (&str)[N]) noexcept :
+        CompileInAddr<Subnet6, CharT, N>(str)
+    {}
+};
+
+template <typename CharT, std::size_t N>
+struct CompileSubnetAny : CompileInAddr<SubnetAny, CharT, N>
+{
+    constexpr CompileSubnetAny(const CharT (&str)[N]) noexcept :
+        CompileInAddr<SubnetAny, CharT, N>(str)
+    {}
+};
+
+} // namespace detail
+
+inline namespace subnet_literals
+{
+
+template <detail::CompileSubnet4 Str>
+constexpr auto operator"" _sub4() noexcept
+{
+    static_assert(Str.valid, "stdplus::Subnet4");
+    return Subnet4(Str.addr, Str.pfx);
+}
+
+template <detail::CompileSubnet6 Str>
+constexpr auto operator"" _sub6() noexcept
+{
+    static_assert(Str.valid, "stdplus::Subnet6");
+    return Subnet6(Str.addr, Str.pfx);
+}
+
+template <detail::CompileSubnetAny Str>
+constexpr auto operator"" _sub() noexcept
+{
+    static_assert(Str.valid, "stdplus::SubnetAny");
+    return Str.v4 ? SubnetAny(Str.u.addr4, Str.pfx)
+                  : SubnetAny(Str.u.addr6, Str.pfx);
+}
+
+} // namespace subnet_literals
 } // namespace stdplus
 
 template <stdplus::Subnet Sub, typename CharT>
diff --git a/test/net/addr/subnet.cpp b/test/net/addr/subnet.cpp
index ca2fa20..31bad90 100644
--- a/test/net/addr/subnet.cpp
+++ b/test/net/addr/subnet.cpp
@@ -64,6 +64,7 @@
     EXPECT_THROW(fs("0.0.0.0/"sv), std::invalid_argument);
     EXPECT_THROW(fs("::/80"sv), std::invalid_argument);
     EXPECT_EQ((SubnetAny{in_addr{}, 30}), fs("0.0.0.0/30"sv));
+    EXPECT_EQ((SubnetAny{in_addr{}, 30}), "0.0.0.0/30"_sub4);
 }
 
 TEST(Subnet4, ToStr)
@@ -135,6 +136,7 @@
     EXPECT_THROW(fs("::/"sv), std::invalid_argument);
     EXPECT_THROW(fs("0.0.0.0/0"sv), std::invalid_argument);
     EXPECT_EQ((Subnet6{in6_addr{}, 80}), fs("::/80"sv));
+    EXPECT_EQ((Subnet6{in6_addr{}, 80}), "::/80"_sub6);
 }
 
 TEST(Subnet6, ToStr)
@@ -210,7 +212,9 @@
     EXPECT_THROW(fs("0.0.0.0/"sv), std::invalid_argument);
     EXPECT_EQ((SubnetAny{in_addr{}, 0}), fs("0.0.0.0/0"sv));
     EXPECT_EQ((SubnetAny{in_addr{}, 30}), fs("0.0.0.0/30"sv));
+    EXPECT_EQ((SubnetAny{in_addr{}, 30}), "0.0.0.0/30"_sub);
     EXPECT_EQ((SubnetAny{in6_addr{}, 80}), fs("::/80"sv));
+    EXPECT_EQ((SubnetAny{in6_addr{}, 80}), "::/80"_sub);
 }
 
 TEST(SubnetAny, ToStr)