From 4c5fdb541f36211361a05595a3d89fb0afcbec50 Mon Sep 17 00:00:00 2001 From: Galik Date: Mon, 18 Sep 2017 23:20:51 +0100 Subject: [PATCH] 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 * Clang format tests/string_span_tests.cpp * Removed ensure_z() overloads as they don't add functionality. --- include/gsl/string_span | 150 +++++++++---------------- tests/string_span_tests.cpp | 218 +++++++++++++++++++++++++++++++++++- 2 files changed, 265 insertions(+), 103 deletions(-) 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 @@ -71,31 +71,32 @@ using czstring = basic_zstring; template using cwzstring = basic_zstring; +template +using cu16zstring = basic_zstring; + +template +using cu32zstring = basic_zstring; + template using zstring = basic_zstring; template using wzstring = basic_zstring; +template +using u16zstring = basic_zstring; + +template +using u32zstring = basic_zstring; + namespace details { - inline std::ptrdiff_t string_length(const char* str, std::ptrdiff_t n) + template + std::ptrdiff_t string_length(const CharT* str, std::ptrdiff_t n) { if (str == nullptr || n <= 0) return 0; - span 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 str_span{str, n}; + span str_span{str, n}; std::ptrdiff_t len = 0; while (len < n && str_span[len]) len++; @@ -122,48 +123,18 @@ span ensure_sentinel(T* seq, std::ptrdiff_t max = PTRDIFF_MAX } // -// 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 -inline span ensure_z(T* const& sz, std::ptrdiff_t max = PTRDIFF_MAX) +template +inline span ensure_z(CharT* const& sz, std::ptrdiff_t max = PTRDIFF_MAX) { - return ensure_sentinel(sz, max); + return ensure_sentinel(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 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 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 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 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 -span ensure_z(T (&sz)[N]) +template +span ensure_z(CharT (&sz)[N]) { return ensure_z(&sz[0], static_cast(N)); } @@ -194,47 +165,6 @@ namespace details struct is_basic_string_span : is_basic_string_span_oracle> { }; - - template - struct length_func - { - }; - - template <> - struct length_func - { - std::ptrdiff_t operator()(const char* const ptr, std::ptrdiff_t length) GSL_NOEXCEPT - { - return details::string_length(ptr, length); - } - }; - - template <> - struct length_func - { - 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 - { - std::ptrdiff_t operator()(const char* const ptr, std::ptrdiff_t length) GSL_NOEXCEPT - { - return details::string_length(ptr, length); - } - }; - - template <> - struct length_func - { - 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 @@ public: // 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 @@ public: private: static impl_type remove_z(pointer const& sz, std::ptrdiff_t max) { - return {sz, details::length_func()(sz, max)}; + return {sz, details::string_length(sz, max)}; } template @@ -423,6 +353,18 @@ using wstring_span = basic_string_span; template using cwstring_span = basic_string_span; +template +using u16string_span = basic_string_span; + +template +using cu16string_span = basic_string_span; + +template +using u32string_span = basic_string_span; + +template +using cu32string_span = basic_string_span; + // // to_string() allow (explicit) conversions from string_span to string // @@ -468,13 +410,13 @@ public: // 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; } @@ -499,12 +441,24 @@ using zstring_span = basic_zstring_span; template using wzstring_span = basic_zstring_span; +template +using u16zstring_span = basic_zstring_span; + +template +using u32zstring_span = basic_zstring_span; + template using czstring_span = basic_zstring_span; template using cwzstring_span = basic_zstring_span; +template +using cu16zstring_span = basic_zstring_span; + +template +using cu32zstring_span = basic_zstring_span; + // operator == template //owner #include +#include #include #include #include @@ -26,6 +27,27 @@ using namespace std; using namespace gsl; +// Generic string functions + +namespace generic +{ + +template +auto strlen(const CharT* s) +{ + auto p = s; + while (*p) ++p; + return p - s; +} + +template +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 @@ TEST_CASE("Constructors") } template -T move_wrapper(T && t) +T move_wrapper(T&& t) { return std::move(t); } @@ -874,7 +896,7 @@ TEST_CASE("zstring") 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 @@ TEST_CASE("zstring") 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 @@ TEST_CASE("wzstring") 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 @@ TEST_CASE("wzstring") 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 @@ TEST_CASE("Issue305") 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 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); +}