zstring: Add class
This is the most efficient wrapper for nul-terminated strings as it does
not care about determining string length. Conversions to / from
ztring_view are provided to make it trivial to upgrade.
Change-Id: Ic83626c53b95b851c3c4a13b1eded8f1e40539f0
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index 91ea750..67ad4f7 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -6,6 +6,7 @@
'stdplus/flags.hpp',
'stdplus/raw.hpp',
'stdplus/signal.hpp',
+ 'stdplus/zstring.hpp',
'stdplus/zstring_view.hpp',
subdir: 'stdplus')
diff --git a/include/stdplus/zstring.hpp b/include/stdplus/zstring.hpp
new file mode 100644
index 0000000..28a3494
--- /dev/null
+++ b/include/stdplus/zstring.hpp
@@ -0,0 +1,241 @@
+#pragma once
+#include <cstddef>
+#include <limits>
+#include <string>
+#include <type_traits>
+#ifndef NDEBUG
+#include <stdexcept>
+#endif
+
+namespace stdplus
+{
+namespace detail
+{
+
+template <typename CharT, std::size_t N>
+constexpr bool zstring_validate(const CharT (&str)[N]) noexcept
+{
+ for (size_t i = 0; i < N - 1; ++i)
+ {
+ if (str[i] == '\0')
+ {
+ return false;
+ }
+ }
+ return str[N - 1] == '\0';
+}
+
+template <typename CharT, typename Traits>
+inline constexpr bool
+ zstring_validate(const std::basic_string<CharT, Traits>& str) noexcept
+{
+ return str.find('\0') == str.npos;
+}
+
+} // namespace detail
+
+/**
+ * @brief Provides a class for expressing an unbounded length string that has
+ * a nul terminating character. This is different from zstring_view in that it
+ * never computes the length, which is slightly cheaper if the string will just
+ * be downcast to a `const char *`.
+ */
+template <typename CharT,
+ typename Traits = std::char_traits<std::remove_const_t<CharT>>>
+class basic_zstring
+{
+ private:
+ using decay_t = std::remove_const_t<CharT>;
+
+ public:
+ using traits_type = Traits;
+ using value_type = CharT;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using size_type = std::size_t;
+
+ template <typename T, size_type N,
+ std::enable_if_t<std::is_same_v<decay_t, std::remove_cvref_t<T>>,
+ bool> = true>
+ constexpr basic_zstring(T (&str)[N])
+#ifdef NDEBUG
+ noexcept
+#endif
+ :
+ data_(str)
+ {
+#ifndef NDEBUG
+ if (!detail::zstring_validate(str))
+ {
+ throw std::invalid_argument("stdplus::zstring");
+ }
+#endif
+ }
+ template <typename T, std::enable_if_t<std::is_pointer_v<T>, bool> = true>
+ inline constexpr basic_zstring(T str) noexcept : data_(str)
+ {
+ }
+ template <typename T,
+ std::enable_if_t<
+ std::is_same_v<std::basic_string<decay_t, Traits,
+ typename T::allocator_type>,
+ std::remove_cvref_t<T>>,
+ bool> = true>
+ constexpr basic_zstring(T& str)
+#ifdef NDEBUG
+ noexcept
+#endif
+ :
+ data_(str.data())
+ {
+#ifndef NDEBUG
+ if (!detail::zstring_validate(str))
+ {
+ throw std::invalid_argument("stdplus::zstring");
+ }
+#endif
+ }
+
+ inline constexpr
+ operator basic_zstring<const CharT, Traits>() const noexcept
+ {
+ return basic_zstring<const CharT, Traits>(data_);
+ }
+
+ inline constexpr reference operator[](size_type pos) const noexcept
+ {
+ return data_[pos];
+ }
+ inline constexpr reference front() const noexcept
+ {
+ return data_[0];
+ }
+ inline constexpr pointer data() const noexcept
+ {
+ return data_;
+ }
+ inline constexpr const_pointer c_str() const noexcept
+ {
+ return data_;
+ }
+ [[nodiscard]] constexpr bool empty() const noexcept
+ {
+ return data_ == nullptr || data_[0] == '\0';
+ }
+
+ inline constexpr basic_zstring suffix(size_type size) const noexcept
+ {
+ return data_ + size;
+ }
+ inline constexpr basic_zstring<const CharT, Traits>
+ csuffix(size_type size) const noexcept
+ {
+ return data_ + size;
+ }
+
+ constexpr int compare(const_pointer other,
+ size_type other_size) const noexcept
+ {
+ for (size_t i = 0; i < other_size; ++i)
+ {
+ if (data_[i] == '\0')
+ {
+ return -1;
+ }
+ if (!Traits::eq(other[i], data_[i]))
+ {
+ return Traits::lt(other[i], data_[i]) ? 1 : -1;
+ }
+ }
+ if (data_[other_size] == '\0')
+ {
+ return 0;
+ }
+ return 1;
+ }
+ constexpr int compare(const_pointer other) const noexcept
+ {
+ auto data = data_;
+ while (true)
+ {
+ if (data[0] == '\0')
+ {
+ if (other[0] == '\0')
+ {
+ return 0;
+ }
+ return -1;
+ }
+ if (other[0] == '\0')
+ {
+ return 1;
+ }
+ if (!Traits::eq(other[0], data[0]))
+ {
+ return Traits::lt(other[0], data[0]) ? 1 : -1;
+ }
+ data++;
+ other++;
+ }
+ }
+
+ template <typename CharT1>
+ inline constexpr Traits::comparison_category
+ operator<=>(basic_zstring<CharT1, Traits> rhs) const noexcept
+ {
+ 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
+ {
+ return compare(rhs) <=> 0;
+ }
+ template <typename Allocator>
+ inline constexpr Traits::comparison_category operator<=>(
+ const std::basic_string<decay_t, Traits, Allocator>& rhs) const noexcept
+ {
+ return compare(rhs.data(), rhs.size()) <=> 0;
+ }
+ inline constexpr Traits::comparison_category
+ operator<=>(std::basic_string_view<decay_t, Traits> rhs) const noexcept
+ {
+ return compare(rhs.data(), rhs.size()) <=> 0;
+ }
+
+ inline constexpr bool operator==(const auto& rhs) const noexcept
+ {
+ return (*this <=> rhs) == 0;
+ }
+
+ private:
+ pointer data_;
+};
+
+template <typename CharT, typename Traits>
+std::basic_ostream<std::remove_const_t<CharT>, Traits>&
+ operator<<(std::basic_ostream<std::remove_const_t<CharT>, Traits>& os,
+ basic_zstring<CharT, Traits> v)
+{
+ return os << v.c_str();
+}
+
+#define zstring_all(char_t, pfx) \
+ using pfx##zstring = basic_zstring<char_t>; \
+ using const_##pfx##zstring = basic_zstring<const char_t>;
+zstring_all(char, );
+zstring_all(char8_t, u8);
+zstring_all(char16_t, u16);
+zstring_all(char32_t, u32);
+zstring_all(wchar_t, w);
+#undef zstring_all
+
+} // namespace stdplus
diff --git a/include/stdplus/zstring_view.hpp b/include/stdplus/zstring_view.hpp
index babb1c2..c97364c 100644
--- a/include/stdplus/zstring_view.hpp
+++ b/include/stdplus/zstring_view.hpp
@@ -1,5 +1,6 @@
#pragma once
#include <stdexcept>
+#include <stdplus/zstring.hpp>
#include <string>
#include <string_view>
#include <type_traits>
@@ -27,19 +28,6 @@
}
template <typename CharT, std::size_t N>
-constexpr bool zstring_validate(const CharT (&str)[N]) noexcept
-{
- for (size_t i = 0; i < N - 1; ++i)
- {
- if (str[i] == '\0')
- {
- return false;
- }
- }
- return str[N - 1] == '\0';
-}
-
-template <typename CharT, std::size_t N>
struct compile_zstring_view
{
CharT data[N] = {};
@@ -67,6 +55,7 @@
{
private:
using string_view_base = std::basic_string_view<CharT, Traits>;
+ using zstring_base = basic_zstring<const CharT, Traits>;
public:
using traits_type = string_view_base::traits_type;
@@ -120,18 +109,31 @@
sv(str.data())
{
#ifndef NDEBUG
- if (str.find('\0') != npos)
+ if (!detail::zstring_validate(str))
{
throw std::invalid_argument("stdplus::zstring_view");
}
#endif
}
+ template <
+ typename T,
+ std::enable_if_t<std::is_same_v<value_type, std::remove_const_t<T>>,
+ bool> = true>
+ inline constexpr basic_zstring_view(basic_zstring<T, Traits> str) noexcept :
+ sv(str.data())
+ {
+ }
inline constexpr operator string_view_base() const noexcept
{
return sv;
}
+ inline constexpr operator zstring_base() const noexcept
+ {
+ return zstring_base(data());
+ }
+
inline constexpr const_iterator begin() const noexcept
{
return sv.begin();
diff --git a/src/meson.build b/src/meson.build
index a59cffd..204110f 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -44,6 +44,7 @@
stdplus_srcs = [
'exception.cpp',
'signal.cpp',
+ 'zstring.cpp',
'zstring_view.cpp',
]
diff --git a/src/zstring.cpp b/src/zstring.cpp
new file mode 100644
index 0000000..660b81c
--- /dev/null
+++ b/src/zstring.cpp
@@ -0,0 +1,16 @@
+#include <stdplus/zstring.hpp>
+
+namespace stdplus
+{
+
+#define zstring_instance(char_t) \
+ template class basic_zstring<char_t>; \
+ template class basic_zstring<const char_t>
+zstring_instance(char);
+zstring_instance(char8_t);
+zstring_instance(char16_t);
+zstring_instance(char32_t);
+zstring_instance(wchar_t);
+#undef zstring_instance
+
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index ade258e..2a6645f 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -7,6 +7,7 @@
'signal': [stdplus_dep, gtest_main_dep],
'util/cexec': [stdplus_dep, gtest_main_dep],
'util/string': [stdplus_dep, gtest_main_dep],
+ 'zstring': [stdplus_dep, gtest_main_dep],
'zstring_view': [stdplus_dep, gtest_main_dep],
}
diff --git a/test/zstring.cpp b/test/zstring.cpp
new file mode 100644
index 0000000..07ea1f1
--- /dev/null
+++ b/test/zstring.cpp
@@ -0,0 +1,103 @@
+#include <gtest/gtest.h>
+#include <iostream>
+#include <stdplus/zstring.hpp>
+#include <string>
+#include <string_view>
+
+using std::literals::string_literals::operator""s;
+using std::literals::string_view_literals::operator""sv;
+
+namespace stdplus
+{
+
+TEST(Zstring, Construct)
+{
+ auto str = "a\0"s;
+ const auto cstr = str;
+#ifdef NDEBUG
+ EXPECT_EQ("a", zstring(str));
+ EXPECT_EQ("a", const_zstring(str));
+ EXPECT_EQ("a", const_zstring(cstr));
+#else
+ EXPECT_THROW((zstring(str)), std::invalid_argument);
+ 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));
+
+ std::cerr << const_zstring(as);
+}
+
+TEST(Zstring, NoTypeCoercion)
+{
+ char empty[] = "";
+ auto zs = zstring(empty);
+ auto czs = const_zstring("");
+
+ EXPECT_NE(zs, "\0");
+ EXPECT_NE("\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_NE(czs, "\0"sv);
+ EXPECT_NE("\0"sv, czs);
+ EXPECT_LT(czs, "\0"sv);
+ EXPECT_GT("\0"sv, czs);
+
+ auto str = "\0"s;
+ EXPECT_NE(zs, str);
+ EXPECT_NE(str, zs);
+ EXPECT_LT(zs, str);
+ EXPECT_GT(str, zs);
+ EXPECT_NE(czs, str);
+ EXPECT_NE(str, czs);
+ EXPECT_LT(czs, str);
+ EXPECT_GT(str, czs);
+}
+
+TEST(Zstring, Comparison)
+{
+ char mut[] = "ac";
+ auto zs = zstring(mut);
+ auto czs = const_zstring("ac");
+
+#define test(cmp, rcmp, str) \
+ EXPECT_##cmp(zs, const_zstring(str)); \
+ EXPECT_##rcmp(const_zstring(str), zs); \
+ EXPECT_##cmp(czs, const_zstring(str)); \
+ EXPECT_##rcmp(const_zstring(str), czs); \
+ EXPECT_##cmp(zs, str); \
+ EXPECT_##rcmp(str, zs); \
+ EXPECT_##cmp(czs, str); \
+ EXPECT_##rcmp(str, czs); \
+ EXPECT_##cmp(zs, str##sv); \
+ EXPECT_##rcmp(str##sv, zs); \
+ EXPECT_##cmp(czs, str##sv); \
+ EXPECT_##rcmp(str##sv, czs); \
+ EXPECT_##cmp(zs, str##s); \
+ EXPECT_##rcmp(str##s, zs); \
+ EXPECT_##cmp(czs, str##s); \
+ EXPECT_##rcmp(str##s, czs); \
+ EXPECT_##cmp(zs, reinterpret_cast<const char*>(str)); \
+ EXPECT_##rcmp(reinterpret_cast<const char*>(str), zs); \
+ EXPECT_##cmp(czs, reinterpret_cast<const char*>(str)); \
+ EXPECT_##rcmp(reinterpret_cast<const char*>(str), czs)
+ test(EQ, EQ, "ac");
+ test(GT, LT, "a");
+ test(LT, GT, "acb");
+ test(LT, GT, "ad");
+ test(GT, LT, "ab");
+#undef test
+}
+
+} // namespace stdplus