Made string_span details::string_length() generic (Fix issue #542) (#543)

* Made string_span details::string_length() generic

removed overloads & specialized classes

Creating string_spans using `char16_t` and `char32_t` was not possible
without creating new specializations and function overloads.

This patch makes details::string_length() generic removing the need to
extend the overloads and specializations.

* added type aliases for string_span types char16_t and char32_t

* Added char16_t & char32_t overloads for ensure_z

* added string_span tests for char16_T & char32_t

* added zstring type aliases for char16_t & char32_t

* Added tests for char16_t & char31_t zstring and string_span types

* applies clang format to <gsl/string_span>

* Clang format tests/string_span_tests.cpp

* Removed ensure_z() overloads as they don't add functionality.
diff --git a/include/gsl/string_span b/include/gsl/string_span
index 26ae3e8..476403a 100644
--- a/include/gsl/string_span
+++ b/include/gsl/string_span
@@ -39,8 +39,8 @@
 #pragma push_macro("constexpr")
 #define constexpr /*constexpr*/
 
-#endif           // _MSC_VER < 1910
-#endif           // _MSC_VER
+#endif // _MSC_VER < 1910
+#endif // _MSC_VER
 
 // In order to test the library, we need it to throw exceptions that we can catch
 #ifdef GSL_THROW_ON_CONTRACT_VIOLATION
@@ -72,30 +72,31 @@
 using cwzstring = basic_zstring<const wchar_t, Extent>;
 
 template <std::ptrdiff_t Extent = dynamic_extent>
+using cu16zstring = basic_zstring<const char16_t, Extent>;
+
+template <std::ptrdiff_t Extent = dynamic_extent>
+using cu32zstring = basic_zstring<const char32_t, Extent>;
+
+template <std::ptrdiff_t Extent = dynamic_extent>
 using zstring = basic_zstring<char, Extent>;
 
 template <std::ptrdiff_t Extent = dynamic_extent>
 using wzstring = basic_zstring<wchar_t, Extent>;
 
+template <std::ptrdiff_t Extent = dynamic_extent>
+using u16zstring = basic_zstring<char16_t, Extent>;
+
+template <std::ptrdiff_t Extent = dynamic_extent>
+using u32zstring = basic_zstring<char32_t, Extent>;
+
 namespace details
 {
-    inline std::ptrdiff_t string_length(const char* str, std::ptrdiff_t n)
+    template <class CharT>
+    std::ptrdiff_t string_length(const CharT* str, std::ptrdiff_t n)
     {
         if (str == nullptr || n <= 0) return 0;
 
-        span<const char> str_span{str, n};
-
-        std::ptrdiff_t len = 0;
-        while (len < n && str_span[len]) len++;
-
-        return len;
-    }
-
-    inline std::ptrdiff_t wstring_length(const wchar_t* str, std::ptrdiff_t n)
-    {
-        if (str == nullptr || n <= 0) return 0;
-
-        span<const wchar_t> str_span{str, n};
+        span<const CharT> str_span{str, n};
 
         std::ptrdiff_t len = 0;
         while (len < n && str_span[len]) len++;
@@ -122,48 +123,18 @@
 }
 
 //
-// ensure_z - creates a span for a czstring or cwzstring.
+// ensure_z - creates a span for a zero terminated strings.
 // Will fail fast if a null-terminator cannot be found before
 // the limit of size_type.
 //
-template <typename T>
-inline span<T, dynamic_extent> ensure_z(T* const& sz, std::ptrdiff_t max = PTRDIFF_MAX)
+template <typename CharT>
+inline span<CharT, dynamic_extent> ensure_z(CharT* const& sz, std::ptrdiff_t max = PTRDIFF_MAX)
 {
-    return ensure_sentinel<T, 0>(sz, max);
+    return ensure_sentinel<CharT, CharT(0)>(sz, max);
 }
 
-// TODO (neilmac) there is probably a better template-magic way to get the const and non-const
-// overloads to share an implementation
-inline span<char, dynamic_extent> ensure_z(char* const& sz, std::ptrdiff_t max)
-{
-    auto len = details::string_length(sz, max);
-    Ensures(sz[len] == 0);
-    return {sz, len};
-}
-
-inline span<const char, dynamic_extent> ensure_z(const char* const& sz, std::ptrdiff_t max)
-{
-    auto len = details::string_length(sz, max);
-    Ensures(sz[len] == 0);
-    return {sz, len};
-}
-
-inline span<wchar_t, dynamic_extent> ensure_z(wchar_t* const& sz, std::ptrdiff_t max)
-{
-    auto len = details::wstring_length(sz, max);
-    Ensures(sz[len] == 0);
-    return {sz, len};
-}
-
-inline span<const wchar_t, dynamic_extent> ensure_z(const wchar_t* const& sz, std::ptrdiff_t max)
-{
-    auto len = details::wstring_length(sz, max);
-    Ensures(sz[len] == 0);
-    return {sz, len};
-}
-
-template <typename T, std::size_t N>
-span<T, dynamic_extent> ensure_z(T (&sz)[N])
+template <typename CharT, std::size_t N>
+span<CharT, dynamic_extent> ensure_z(CharT (&sz)[N])
 {
     return ensure_z(&sz[0], static_cast<std::ptrdiff_t>(N));
 }
@@ -194,47 +165,6 @@
     struct is_basic_string_span : is_basic_string_span_oracle<std::remove_cv_t<T>>
     {
     };
-
-    template <typename T>
-    struct length_func
-    {
-    };
-
-    template <>
-    struct length_func<char>
-    {
-        std::ptrdiff_t operator()(const char* const ptr, std::ptrdiff_t length) GSL_NOEXCEPT
-        {
-            return details::string_length(ptr, length);
-        }
-    };
-
-    template <>
-    struct length_func<wchar_t>
-    {
-        std::ptrdiff_t operator()(const wchar_t* const ptr, std::ptrdiff_t length) GSL_NOEXCEPT
-        {
-            return details::wstring_length(ptr, length);
-        }
-    };
-
-    template <>
-    struct length_func<const char>
-    {
-        std::ptrdiff_t operator()(const char* const ptr, std::ptrdiff_t length) GSL_NOEXCEPT
-        {
-            return details::string_length(ptr, length);
-        }
-    };
-
-    template <>
-    struct length_func<const wchar_t>
-    {
-        std::ptrdiff_t operator()(const wchar_t* const ptr, std::ptrdiff_t length) GSL_NOEXCEPT
-        {
-            return details::wstring_length(ptr, length);
-        }
-    };
 }
 
 //
@@ -262,13 +192,13 @@
     // copy
     constexpr basic_string_span(const basic_string_span& other) GSL_NOEXCEPT = default;
 
-// move
+    // move
     constexpr basic_string_span(basic_string_span&& other) GSL_NOEXCEPT = default;
 
     // assign
     constexpr basic_string_span& operator=(const basic_string_span& other) GSL_NOEXCEPT = default;
 
-// move assign
+    // move assign
     constexpr basic_string_span& operator=(basic_string_span&& other) GSL_NOEXCEPT = default;
 
     // from nullptr
@@ -399,7 +329,7 @@
 private:
     static impl_type remove_z(pointer const& sz, std::ptrdiff_t max)
     {
-        return {sz, details::length_func<element_type>()(sz, max)};
+        return {sz, details::string_length(sz, max)};
     }
 
     template <std::size_t N>
@@ -423,6 +353,18 @@
 template <std::ptrdiff_t Extent = dynamic_extent>
 using cwstring_span = basic_string_span<const wchar_t, Extent>;
 
+template <std::ptrdiff_t Extent = dynamic_extent>
+using u16string_span = basic_string_span<char16_t, Extent>;
+
+template <std::ptrdiff_t Extent = dynamic_extent>
+using cu16string_span = basic_string_span<const char16_t, Extent>;
+
+template <std::ptrdiff_t Extent = dynamic_extent>
+using u32string_span = basic_string_span<char32_t, Extent>;
+
+template <std::ptrdiff_t Extent = dynamic_extent>
+using cu32string_span = basic_string_span<const char32_t, Extent>;
+
 //
 // to_string() allow (explicit) conversions from string_span to string
 //
@@ -468,13 +410,13 @@
     // copy
     constexpr basic_zstring_span(const basic_zstring_span& other) = default;
 
-// move
+    // move
     constexpr basic_zstring_span(basic_zstring_span&& other) = default;
 
     // assign
     constexpr basic_zstring_span& operator=(const basic_zstring_span& other) = default;
 
-// move assign
+    // move assign
     constexpr basic_zstring_span& operator=(basic_zstring_span&& other) = default;
 
     constexpr bool empty() const GSL_NOEXCEPT { return span_.size() == 0; }
@@ -500,11 +442,23 @@
 using wzstring_span = basic_zstring_span<wchar_t, Max>;
 
 template <std::ptrdiff_t Max = dynamic_extent>
+using u16zstring_span = basic_zstring_span<char16_t, Max>;
+
+template <std::ptrdiff_t Max = dynamic_extent>
+using u32zstring_span = basic_zstring_span<char32_t, Max>;
+
+template <std::ptrdiff_t Max = dynamic_extent>
 using czstring_span = basic_zstring_span<const char, Max>;
 
 template <std::ptrdiff_t Max = dynamic_extent>
 using cwzstring_span = basic_zstring_span<const wchar_t, Max>;
 
+template <std::ptrdiff_t Max = dynamic_extent>
+using cu16zstring_span = basic_zstring_span<const char16_t, Max>;
+
+template <std::ptrdiff_t Max = dynamic_extent>
+using cu32zstring_span = basic_zstring_span<const char32_t, Max>;
+
 // operator ==
 template <class CharT, std::ptrdiff_t Extent, class T,
           class = std::enable_if_t<
diff --git a/tests/string_span_tests.cpp b/tests/string_span_tests.cpp
index 229a117..823b19d 100644
--- a/tests/string_span_tests.cpp
+++ b/tests/string_span_tests.cpp
@@ -19,6 +19,7 @@
 #include <gsl/gsl> //owner
 #include <gsl/string_span>
 
+#include <algorithm>
 #include <cstdlib>
 #include <map>
 #include <vector>
@@ -26,6 +27,27 @@
 using namespace std;
 using namespace gsl;
 
+// Generic string functions
+
+namespace generic
+{
+
+template <typename CharT>
+auto strlen(const CharT* s)
+{
+    auto p = s;
+    while (*p) ++p;
+    return p - s;
+}
+
+template <typename CharT>
+auto strnlen(const CharT* s, std::size_t n)
+{
+    return std::find(s, s + n, CharT(0)) - s;
+}
+
+} // namespace generic
+
 TEST_CASE("TestLiteralConstruction")
 {
     cwstring_span<> v = ensure_z(L"Hello");
@@ -748,7 +770,7 @@
 }
 
 template <typename T>
-T move_wrapper(T && t)
+T move_wrapper(T&& t)
 {
     return std::move(t);
 }
@@ -874,7 +896,7 @@
 
         zstring_span<> zspan({buf, 1});
 
-        CHECK(strlen(zspan.assume_z()) == 0);
+        CHECK(generic::strlen(zspan.assume_z()) == 0);
         CHECK(zspan.as_string_span().size() == 0);
         CHECK(zspan.ensure_z().size() == 0);
     }
@@ -895,7 +917,7 @@
         auto name = CreateTempName({buf, 10});
         if (!name.empty()) {
             czstring<> str = name.assume_z();
-            CHECK(strlen(str) == 3);
+            CHECK(generic::strlen(str) == 3);
             CHECK(*(str + 3) == '\0');
         }
     }
@@ -928,7 +950,7 @@
 
         wzstring_span<> zspan({buf, 1});
 
-        CHECK(wcsnlen(zspan.assume_z(), 1) == 0);
+        CHECK(generic::strnlen(zspan.assume_z(), 1) == 0);
         CHECK(zspan.as_string_span().size() == 0);
         CHECK(zspan.ensure_z().size() == 0);
     }
@@ -949,7 +971,115 @@
         const auto name = CreateTempNameW({buf, 10});
         if (!name.empty()) {
             cwzstring<> str = name.assume_z();
-            CHECK(wcsnlen(str, 10) == 3);
+            CHECK(generic::strnlen(str, 10) == 3);
+            CHECK(*(str + 3) == L'\0');
+        }
+    }
+}
+
+cu16zstring_span<> CreateTempNameU16(u16string_span<> span)
+{
+    Expects(span.size() > 1);
+
+    int last = 0;
+    if (span.size() > 4) {
+        span[0] = u't';
+        span[1] = u'm';
+        span[2] = u'p';
+        last = 3;
+    }
+    span[last] = u'\0';
+
+    auto ret = span.subspan(0, 4);
+    return {ret};
+}
+
+TEST_CASE("u16zstring")
+{
+
+    // create zspan from zero terminated string
+    {
+        char16_t buf[1];
+        buf[0] = L'\0';
+
+        u16zstring_span<> zspan({buf, 1});
+
+        CHECK(generic::strnlen(zspan.assume_z(), 1) == 0);
+        CHECK(zspan.as_string_span().size() == 0);
+        CHECK(zspan.ensure_z().size() == 0);
+    }
+
+    // create zspan from non-zero terminated string
+    {
+        char16_t buf[1];
+        buf[0] = u'a';
+
+        const auto workaround_macro = [&]() { u16zstring_span<> zspan({buf, 1}); };
+        CHECK_THROWS_AS(workaround_macro(), fail_fast);
+    }
+
+    // usage scenario: create zero-terminated temp file name and pass to a legacy API
+    {
+        char16_t buf[10];
+
+        const auto name = CreateTempNameU16({buf, 10});
+        if (!name.empty()) {
+            cu16zstring<> str = name.assume_z();
+            CHECK(generic::strnlen(str, 10) == 3);
+            CHECK(*(str + 3) == L'\0');
+        }
+    }
+}
+
+cu32zstring_span<> CreateTempNameU32(u32string_span<> span)
+{
+    Expects(span.size() > 1);
+
+    int last = 0;
+    if (span.size() > 4) {
+        span[0] = U't';
+        span[1] = U'm';
+        span[2] = U'p';
+        last = 3;
+    }
+    span[last] = U'\0';
+
+    auto ret = span.subspan(0, 4);
+    return {ret};
+}
+
+TEST_CASE("u32zstring")
+{
+
+    // create zspan from zero terminated string
+    {
+        char32_t buf[1];
+        buf[0] = L'\0';
+
+        u32zstring_span<> zspan({buf, 1});
+
+        CHECK(generic::strnlen(zspan.assume_z(), 1) == 0);
+        CHECK(zspan.as_string_span().size() == 0);
+        CHECK(zspan.ensure_z().size() == 0);
+    }
+
+    // create zspan from non-zero terminated string
+    {
+        char32_t buf[1];
+        buf[0] = u'a';
+
+        const auto workaround_macro = [&]() { u32zstring_span<> zspan({buf, 1}); };
+        CHECK_THROWS_AS(workaround_macro(), fail_fast);
+    }
+
+    // usage scenario: create zero-terminated temp file name and pass to a legacy API
+    {
+        char32_t buf[10];
+
+        const auto name = CreateTempNameU32({buf, 10});
+        if (!name.empty()) {
+            cu32zstring<> str = name.assume_z();
+            CHECK(generic::strnlen(str, 10) == 3);
             CHECK(*(str + 3) == L'\0');
         }
     }
@@ -961,3 +1091,81 @@
     CHECK(foo["foo"] == 0);
     CHECK(foo["bar"] == 1);
 }
+
+TEST_CASE("char16_t type")
+{
+    gsl::cu16string_span<> ss1 = gsl::ensure_z(u"abc");
+    CHECK(ss1.size() == 3);
+    CHECK(ss1.size_bytes() == 6);
+
+    std::u16string s1 = gsl::to_string(ss1);
+    CHECK(s1 == u"abc");
+
+    std::u16string s2 = u"abc";
+    gsl::u16string_span<> ss2 = s2;
+    CHECK(ss2.size() == 3);
+
+    gsl::u16string_span<> ss3 = ss2.subspan(1, 1);
+    CHECK(ss3.size() == 1);
+    CHECK(ss3[0] == u'b');
+
+    char16_t buf[4]{u'a', u'b', u'c', u'\0'};
+    gsl::u16string_span<> ss4{buf, 4};
+    CHECK(ss4[3] == u'\0');
+
+    gsl::cu16zstring_span<> ss5(u"abc");
+    CHECK(ss5.as_string_span().size() == 3);
+
+    gsl::cu16string_span<> ss6 = ss5.as_string_span();
+    CHECK(ss6 == ss1);
+
+    std::vector<char16_t> v7 = {u'a', u'b', u'c'};
+    gsl::cu16string_span<> ss7{v7};
+    CHECK(ss7 == ss1);
+
+    gsl::cu16string_span<> ss8 = gsl::ensure_z(u"abc");
+    gsl::cu16string_span<> ss9 = gsl::ensure_z(u"abc");
+    CHECK(ss8 == ss9);
+
+    ss9 = gsl::ensure_z(u"abd");
+    CHECK(ss8 < ss9);
+    CHECK(ss8 <= ss9);
+    CHECK(ss8 != ss9);
+}
+
+TEST_CASE("char32_t type")
+{
+    gsl::cu32string_span<> ss1 = gsl::ensure_z(U"abc");
+    CHECK(ss1.size() == 3);
+    CHECK(ss1.size_bytes() == 12);
+
+    std::u32string s1 = gsl::to_string(ss1);
+    CHECK(s1 == U"abc");
+
+    std::u32string s2 = U"abc";
+    gsl::u32string_span<> ss2 = s2;
+    CHECK(ss2.size() == 3);
+
+    gsl::u32string_span<> ss3 = ss2.subspan(1, 1);
+    CHECK(ss3.size() == 1);
+    CHECK(ss3[0] == U'b');
+
+    char32_t buf[4]{U'a', U'b', U'c', U'\0'};
+    gsl::u32string_span<> ss4{buf, 4};
+    CHECK(ss4[3] == u'\0');
+
+    gsl::cu32zstring_span<> ss5(U"abc");
+    CHECK(ss5.as_string_span().size() == 3);
+
+    gsl::cu32string_span<> ss6 = ss5.as_string_span();
+    CHECK(ss6 == ss1);
+
+    gsl::cu32string_span<> ss8 = gsl::ensure_z(U"abc");
+    gsl::cu32string_span<> ss9 = gsl::ensure_z(U"abc");
+    CHECK(ss8 == ss9);
+
+    ss9 = gsl::ensure_z(U"abd");
+    CHECK(ss8 < ss9);
+    CHECK(ss8 <= ss9);
+    CHECK(ss8 != ss9);
+}