zstring: Fix char array conversion

We don't want to assume the entire char array is used for strings,
otherwise perfectly valid buffers from C are unusable as they always
hold less than or equal to the buffer size of string.

Change-Id: I1354d63b1aa64995ec245edb876fdf6e69be554a
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/stdplus/zstring.hpp b/include/stdplus/zstring.hpp
index 5cf5f0a..2612b09 100644
--- a/include/stdplus/zstring.hpp
+++ b/include/stdplus/zstring.hpp
@@ -13,25 +13,40 @@
 namespace detail
 {
 
-template <typename CharT, std::size_t N>
-constexpr bool zstring_validate(const CharT (&str)[N]) noexcept
+template <typename CharT>
+constexpr std::ptrdiff_t zstring_find_term(const CharT* str, std::size_t min,
+                                           std::size_t max) noexcept
 {
-    for (size_t i = 0; i < N - 1; ++i)
+    for (size_t i = 0; i < min; ++i)
     {
         if (str[i] == '\0')
         {
-            return false;
+            return -1;
         }
     }
-    return str[N - 1] == '\0';
+    for (size_t i = min; i < max; ++i)
+    {
+        if (str[i] == '\0')
+        {
+            return i;
+        }
+    }
+    return -1;
 }
 
-template <typename CharT, typename Traits>
-inline constexpr bool
-    zstring_validate(const std::basic_string<CharT, Traits>& str) noexcept
+#ifndef NDEBUG
+template <typename CharT>
+constexpr std::size_t zstring_validate(const CharT* str, std::size_t min,
+                                       std::size_t max)
 {
-    return str.find('\0') == str.npos;
+    auto ret = zstring_find_term(str, min, max);
+    if (ret < 0)
+    {
+        throw std::invalid_argument("stdplus::zstring");
+    }
+    return ret;
 }
+#endif
 
 template <typename T, typename CharT, typename Traits>
 struct same_string : std::false_type
@@ -69,7 +84,7 @@
     using size_type = std::size_t;
 
     template <typename T, size_type N>
-    constexpr basic_zstring(T (&str)[N])
+    inline constexpr basic_zstring(T (&str)[N])
 #ifdef NDEBUG
         noexcept
 #endif
@@ -77,10 +92,7 @@
         data_(str)
     {
 #ifndef NDEBUG
-        if (!detail::zstring_validate(str))
-        {
-            throw std::invalid_argument("stdplus::zstring");
-        }
+        detail::zstring_validate(str, 0, N);
 #endif
     }
     template <typename T, std::enable_if_t<std::is_pointer_v<T>, bool> = true>
@@ -91,7 +103,7 @@
               std::enable_if_t<detail::same_string<std::remove_cvref_t<T>,
                                                    decay_t, Traits>::value,
                                bool> = true>
-    constexpr basic_zstring(T&& str)
+    inline constexpr basic_zstring(T&& str)
 #ifdef NDEBUG
         noexcept
 #endif
@@ -99,10 +111,7 @@
         data_(str.data())
     {
 #ifndef NDEBUG
-        if (!detail::zstring_validate(str))
-        {
-            throw std::invalid_argument("stdplus::zstring");
-        }
+        detail::zstring_validate(str.data(), str.size(), str.size() + 1);
 #endif
     }
 
@@ -195,12 +204,6 @@
     {
         return compare(rhs.data()) <=> 0;
     }
-    template <typename CharTO, size_type N>
-    inline constexpr Traits::comparison_category
-        operator<=>(const CharTO (&rhs)[N]) const noexcept
-    {
-        return compare(rhs, N - 1) <=> 0;
-    }
     template <typename T, std::enable_if_t<std::is_pointer_v<T>, bool> = true>
     inline constexpr Traits::comparison_category
         operator<=>(T rhs) const noexcept
diff --git a/include/stdplus/zstring_view.hpp b/include/stdplus/zstring_view.hpp
index f7b62dd..616790d 100644
--- a/include/stdplus/zstring_view.hpp
+++ b/include/stdplus/zstring_view.hpp
@@ -36,7 +36,7 @@
 
     constexpr compile_zstring_view(const CharT (&str)[N]) noexcept
     {
-        valid = zstring_validate(str);
+        valid = zstring_find_term(str, N - 1, N) >= 0;
         for (std::size_t i = 0; i < N - 1; ++i)
         {
             data[i] = str[i];
@@ -75,19 +75,15 @@
     static constexpr size_type npos = string_view_base::npos;
 
     template <typename T, size_type N>
-    constexpr basic_zstring_view(T (&str)[N])
+    inline constexpr basic_zstring_view(T (&str)[N])
 #ifdef NDEBUG
-        noexcept
-#endif
+        noexcept :
+        sv(str)
+#else
         :
-        sv(str, N - 1)
-    {
-#ifndef NDEBUG
-        if (!detail::zstring_validate(str))
-        {
-            throw std::invalid_argument("stdplus::zstring_view");
-        }
+        sv(str, detail::zstring_validate(str, 0, N))
 #endif
+    {
     }
     template <typename T, std::enable_if_t<std::is_pointer_v<T>, bool> = true>
     inline constexpr basic_zstring_view(T str) noexcept : sv(str)
@@ -96,19 +92,16 @@
     template <typename T,
               std::enable_if_t<detail::same_string<T, CharT, Traits>::value,
                                bool> = true>
-    constexpr basic_zstring_view(const T& str)
+    inline constexpr basic_zstring_view(const T& str)
 #ifdef NDEBUG
-        noexcept
-#endif
+        noexcept :
+        sv(str)
+#else
         :
-        sv(str.data())
-    {
-#ifndef NDEBUG
-        if (!detail::zstring_validate(str))
-        {
-            throw std::invalid_argument("stdplus::zstring_view");
-        }
+        sv(str.data(),
+           detail::zstring_validate(str.data(), str.size(), str.size() + 1))
 #endif
+    {
     }
     template <
         typename T,
@@ -288,22 +281,11 @@
         return basic_zstring_view(unsafe(), sv.substr(pos));
     }
 
-    template <typename CharTO, size_type N>
-    inline constexpr bool operator==(const CharTO (&rhs)[N]) const noexcept
-    {
-        return *this == std::basic_string_view<CharTO, Traits>(rhs, N - 1);
-    }
     constexpr bool
         operator==(std::basic_string_view<CharT, Traits> rhs) const noexcept
     {
         return size() == rhs.size() && compare(rhs) == 0;
     }
-    template <typename CharTO, size_type N>
-    inline constexpr Traits::comparison_category
-        operator<=>(const CharTO (&rhs)[N]) const noexcept
-    {
-        return *this <=> std::basic_string_view<CharTO, Traits>(rhs, N - 1);
-    }
     constexpr Traits::comparison_category
         operator<=>(std::basic_string_view<CharT, Traits> rhs) const noexcept
     {
diff --git a/src/zstring.cpp b/src/zstring.cpp
index 660b81c..ccddc83 100644
--- a/src/zstring.cpp
+++ b/src/zstring.cpp
@@ -3,7 +3,17 @@
 namespace stdplus
 {
 
+#ifdef NDEBUG
+#define zstring_debug_instance(char_t)
+#else
+#define zstring_debug_instance(char_t)                                         \
+    template std::size_t detail::zstring_validate<char_t>(                     \
+        const char_t* str, std::size_t min, std::size_t max)
+#endif
 #define zstring_instance(char_t)                                               \
+    template std::ptrdiff_t detail::zstring_find_term<char_t>(                 \
+        const char_t* str, std::size_t min, std::size_t max) noexcept;         \
+    zstring_debug_instance(char_t);                                            \
     template class basic_zstring<char_t>;                                      \
     template class basic_zstring<const char_t>
 zstring_instance(char);
@@ -12,5 +22,6 @@
 zstring_instance(char32_t);
 zstring_instance(wchar_t);
 #undef zstring_instance
+#undef zstring_debug_instance
 
 } // namespace stdplus
diff --git a/test/zstring.cpp b/test/zstring.cpp
index 600b493..69b3c9f 100644
--- a/test/zstring.cpp
+++ b/test/zstring.cpp
@@ -24,11 +24,7 @@
     EXPECT_THROW((const_zstring(str)), std::invalid_argument);
     EXPECT_THROW((const_zstring(cstr)), std::invalid_argument);
 #endif
-#ifdef NDEBUG
     EXPECT_EQ("b", const_zstring("b\0"));
-#else
-    EXPECT_THROW(const_zstring("b\0"), std::invalid_argument);
-#endif
     char as[] = "c";
     EXPECT_EQ("c", zstring(as));
     EXPECT_EQ("c", const_zstring(as));
@@ -51,14 +47,14 @@
     auto zs = zstring(empty);
     auto czs = const_zstring("");
 
-    EXPECT_NE(zs, "\0");
-    EXPECT_NE("\0", zs);
+    EXPECT_EQ(zs, "\0");
+    EXPECT_EQ("\0", zs);
     EXPECT_NE(zs, "\0"sv);
     EXPECT_NE("\0"sv, zs);
     EXPECT_LT(zs, "\0"sv);
     EXPECT_GT("\0"sv, zs);
-    EXPECT_NE(czs, "\0");
-    EXPECT_NE("\0", czs);
+    EXPECT_EQ(czs, "\0");
+    EXPECT_EQ("\0", czs);
     EXPECT_NE(czs, "\0"sv);
     EXPECT_NE("\0"sv, czs);
     EXPECT_LT(czs, "\0"sv);
diff --git a/test/zstring_view.cpp b/test/zstring_view.cpp
index 95e9aa5..14a1e04 100644
--- a/test/zstring_view.cpp
+++ b/test/zstring_view.cpp
@@ -67,18 +67,15 @@
 {
     auto s = "hi\0"s;
 #ifdef NDEBUG
-    EXPECT_EQ("hi", zstring_view(s));
+    EXPECT_EQ("hi\0"sv, zstring_view(s));
 #else
     EXPECT_THROW((zstring_view(s)), std::invalid_argument);
 #endif
 
     char mut1[] = "aa\0";
+    EXPECT_EQ("aa", zstring_view(mut1));
+#ifndef NDEBUG
     char mut2[] = {'a', 'a'};
-#ifdef NDEBUG
-    EXPECT_EQ("aa\0", zstring_view(mut1));
-    EXPECT_EQ("a", zstring_view(mut2));
-#else
-    EXPECT_THROW((zstring_view(mut1)), std::invalid_argument);
     EXPECT_THROW((zstring_view(mut2)), std::invalid_argument);
 #endif
 }
@@ -95,8 +92,8 @@
 
 TEST(ZstringView, NoTypeCoercion)
 {
-    EXPECT_NE(""_zsv, "\0");
-    EXPECT_NE("\0", ""_zsv);
+    EXPECT_EQ(""_zsv, "\0");
+    EXPECT_EQ("\0", ""_zsv);
     EXPECT_NE(""_zsv, "\0"sv);
     EXPECT_NE("\0"sv, ""_zsv);
     EXPECT_LT(""_zsv, "\0"sv);