zstring_view: Add class
This adds a string_view variant that guarantees the nul terminating
character of the string is always present. Useful for c-style interface
interop.
Change-Id: Ic918d2f59e2a7a983354a7b0b4755cb033a1db5e
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index 5c6ea12..91ea750 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -6,6 +6,7 @@
'stdplus/flags.hpp',
'stdplus/raw.hpp',
'stdplus/signal.hpp',
+ 'stdplus/zstring_view.hpp',
subdir: 'stdplus')
install_headers(
diff --git a/include/stdplus/zstring_view.hpp b/include/stdplus/zstring_view.hpp
new file mode 100644
index 0000000..782af2e
--- /dev/null
+++ b/include/stdplus/zstring_view.hpp
@@ -0,0 +1,353 @@
+#pragma once
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <type_traits>
+
+namespace stdplus
+{
+
+/**
+ * @brief Provides a class for expressing a string_view that is guaranteed to
+ * have nul termination. Most of the functions of this class are identical to
+ * std::string_view with needed modifier functions removed to guarantee safety.
+ */
+template <typename CharT, typename Traits = std::char_traits<CharT>>
+class basic_zstring_view;
+
+namespace detail
+{
+
+template <typename CharT, typename Traits>
+inline constexpr auto
+ unsafe_zstring_view(std::basic_string_view<CharT, Traits> sv) noexcept
+{
+ using SV = basic_zstring_view<CharT, Traits>;
+ return SV(typename SV::unsafe(), sv);
+}
+
+template <typename CharT, std::size_t N>
+struct compile_zstring_view
+{
+ CharT data[N] = {};
+ bool valid = true;
+
+ constexpr compile_zstring_view(const CharT (&str)[N]) noexcept
+ {
+ for (size_t i = 0; i < N - 1; ++i)
+ {
+ if (str[i] == '\0')
+ {
+ valid = false;
+ }
+ data[i] = str[i];
+ }
+ if (str[N - 1] != '\0')
+ {
+ valid = false;
+ }
+ }
+
+ constexpr auto getzsv() const noexcept
+ {
+ return unsafe_zstring_view(std::basic_string_view<CharT>(data, N - 1));
+ }
+};
+
+} // namespace detail
+
+template <typename CharT, typename Traits>
+class basic_zstring_view
+{
+ private:
+ using string_view_base = std::basic_string_view<CharT, Traits>;
+
+ public:
+ using traits_type = string_view_base::traits_type;
+ using value_type = string_view_base::value_type;
+ using pointer = string_view_base::pointer;
+ using const_pointer = string_view_base::const_pointer;
+ using reference = string_view_base::reference;
+ using const_reference = string_view_base::const_reference;
+ using iterator = string_view_base::iterator;
+ using const_iterator = string_view_base::const_iterator;
+ using reverse_iterator = string_view_base::reverse_iterator;
+ using const_reverse_iterator = string_view_base::const_reverse_iterator;
+ using size_type = string_view_base::size_type;
+ using difference_type = string_view_base::difference_type;
+
+ static constexpr size_type npos = string_view_base::npos;
+
+ inline constexpr basic_zstring_view(const_pointer str) noexcept : sv(str)
+ {
+ }
+ template <typename Allocator>
+ constexpr basic_zstring_view(
+ const std::basic_string<CharT, Traits, Allocator>& str) :
+ sv(str)
+ {
+ if (sv.find('\0') != npos)
+ {
+ throw std::invalid_argument("stdplus::zstring_view");
+ }
+ }
+
+ inline constexpr operator string_view_base() const noexcept
+ {
+ return sv;
+ }
+
+ inline constexpr const_iterator begin() const noexcept
+ {
+ return sv.begin();
+ }
+ inline constexpr const_iterator end() const noexcept
+ {
+ return sv.end();
+ }
+ inline constexpr const_reverse_iterator rbegin() const noexcept
+ {
+ return sv.rbegin();
+ }
+ inline constexpr const_reverse_iterator rend() const noexcept
+ {
+ return sv.rend();
+ }
+
+ inline constexpr const_reference operator[](size_type pos) const noexcept
+ {
+ return sv[pos];
+ }
+ inline constexpr const_reference at(size_type pos) const
+ {
+ return sv.at(pos);
+ }
+ inline constexpr const_reference front() const noexcept
+ {
+ return sv.front();
+ }
+ inline constexpr const_reference back() const noexcept
+ {
+ return sv.back();
+ }
+ inline constexpr const_pointer data() const noexcept
+ {
+ return sv.data();
+ }
+
+ inline constexpr size_type size() const noexcept
+ {
+ return sv.size();
+ }
+ inline constexpr size_type length() const noexcept
+ {
+ return sv.length();
+ }
+ inline constexpr size_type max_size() noexcept
+ {
+ return sv.max_size();
+ }
+ [[nodiscard]] inline constexpr bool empty() const noexcept
+ {
+ return sv.empty();
+ }
+
+ inline constexpr void swap(basic_zstring_view& v) noexcept
+ {
+ sv.swap(v.sv);
+ }
+
+ inline constexpr size_type copy(pointer dest, size_type count,
+ size_type pos = 0) const
+ {
+ return sv.copy(dest, count, pos);
+ }
+ inline constexpr string_view_base substr(size_type pos = 0,
+ size_type count = npos) const
+ {
+ return sv.substr(pos, count);
+ }
+
+ inline constexpr int compare(string_view_base v) const noexcept
+ {
+ return sv.compare(v);
+ }
+ inline constexpr int compare(size_type pos1, size_type count1,
+ string_view_base v) const
+ {
+ return sv.compare(pos1, count1, v);
+ }
+ inline constexpr int compare(size_type pos1, size_type count1,
+ string_view_base v, size_type pos2,
+ size_type count2) const
+ {
+ return sv.compare(pos1, count1, v, pos2, count2);
+ }
+ inline constexpr int compare(const_pointer s) const
+ {
+ return sv.compare(s);
+ }
+ inline constexpr int compare(size_type pos1, size_type count1,
+ const_pointer s) const
+ {
+ return sv.compare(pos1, count1, s);
+ }
+ inline constexpr int compare(size_type pos1, size_type count1,
+ const_pointer s, size_type count2) const
+ {
+ return sv.compare(pos1, count1, s, count2);
+ }
+
+#define zstring_view_has(func) \
+ inline constexpr bool func(string_view_base sv) const noexcept \
+ { \
+ return sv.func(sv); \
+ } \
+ inline constexpr bool func(value_type c) const noexcept \
+ { \
+ return sv.func(c); \
+ } \
+ inline constexpr bool func(const_pointer s) const \
+ { \
+ return sv.func(s); \
+ }
+ zstring_view_has(starts_with);
+ zstring_view_has(ends_with);
+#undef zstring_view_has
+
+#define zstring_view_find(func) \
+ inline constexpr size_type func(string_view_base v, size_type pos = 0) \
+ const noexcept \
+ { \
+ return sv.func(v, pos); \
+ } \
+ inline constexpr size_type func(value_type ch, size_type pos = 0) \
+ const noexcept \
+ { \
+ return sv.func(ch, pos); \
+ } \
+ inline constexpr size_type func(const_pointer s, size_type pos, \
+ size_type count) const \
+ { \
+ return sv.func(s, pos, count); \
+ } \
+ inline constexpr size_type func(const_pointer s, size_type pos = 0) const \
+ { \
+ return sv.func(s, pos); \
+ }
+ zstring_view_find(find);
+ zstring_view_find(rfind);
+ zstring_view_find(find_first_of);
+ zstring_view_find(find_last_of);
+ zstring_view_find(find_first_not_of);
+ zstring_view_find(find_last_not_of);
+#undef zstring_view_find
+
+ inline constexpr const_pointer c_str() const noexcept
+ {
+ return sv.data();
+ }
+ constexpr basic_zstring_view suffix(size_type pos = 0) const
+ {
+ if (pos > sv.size())
+ {
+ throw std::out_of_range("stdplus::zstring_view");
+ }
+ return basic_zstring_view(unsafe(), sv.substr(pos));
+ }
+
+ private:
+ string_view_base sv;
+
+ struct unsafe
+ {
+ };
+ inline constexpr basic_zstring_view(unsafe, string_view_base sv) noexcept :
+ sv(sv)
+ {
+ }
+ friend auto detail::unsafe_zstring_view<CharT, Traits>(string_view_base sv);
+};
+
+template <class CharT, class Traits>
+constexpr bool operator==(basic_zstring_view<CharT, Traits> lhs,
+ basic_zstring_view<CharT, Traits> rhs) noexcept
+{
+ return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
+}
+template <class CharT, class Traits>
+constexpr bool operator==(
+ basic_zstring_view<CharT, Traits> lhs,
+ std::type_identity_t<std::basic_string_view<CharT, Traits>> rhs) noexcept
+{
+ return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
+}
+template <class CharT, class Traits>
+constexpr bool
+ operator==(std::type_identity_t<std::basic_string_view<CharT, Traits>> lhs,
+ basic_zstring_view<CharT, Traits> rhs) noexcept
+{
+ return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
+}
+
+template <class CharT, class Traits>
+constexpr Traits::comparison_category
+ operator<=>(basic_zstring_view<CharT, Traits> lhs,
+ basic_zstring_view<CharT, Traits> rhs) noexcept
+{
+ return lhs.compare(rhs) <=> 0;
+}
+template <class CharT, class Traits>
+constexpr Traits::comparison_category operator<=>(
+ basic_zstring_view<CharT, Traits> lhs,
+ std::type_identity_t<std::basic_string_view<CharT, Traits>> rhs) noexcept
+{
+ return lhs.compare(rhs) <=> 0;
+}
+template <class CharT, class Traits>
+constexpr Traits::comparison_category
+ operator<=>(std::type_identity_t<std::basic_string_view<CharT, Traits>> lhs,
+ basic_zstring_view<CharT, Traits> rhs) noexcept
+{
+ return lhs.compare(rhs) <=> 0;
+}
+
+template <typename CharT, typename Traits>
+std::basic_ostream<CharT, Traits>&
+ operator<<(std::basic_ostream<CharT, Traits>& os,
+ basic_zstring_view<CharT, Traits> v)
+{
+ return os << static_cast<std::basic_string_view<CharT, Traits>>(v);
+}
+
+namespace zstring_view_literals
+{
+template <detail::compile_zstring_view Str>
+inline constexpr auto operator"" _zsv() noexcept
+{
+ static_assert(Str.valid, "stdplus::zstring_view");
+ return Str.getzsv();
+}
+} // namespace zstring_view_literals
+
+} // namespace stdplus
+
+#define zstring_all(char_t, pfx) \
+ namespace stdplus \
+ { \
+ using pfx##zstring_view = basic_zstring_view<char_t>; \
+ } \
+ namespace std \
+ { \
+ template <> \
+ struct hash<stdplus::basic_zstring_view<char_t>> \
+ : hash<basic_string_view<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
diff --git a/src/meson.build b/src/meson.build
index 91fecb5..bdb8f67 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -20,6 +20,7 @@
stdplus_srcs = [
'exception.cpp',
'signal.cpp',
+ 'zstring_view.cpp',
]
if has_fd
diff --git a/src/zstring_view.cpp b/src/zstring_view.cpp
new file mode 100644
index 0000000..f3add60
--- /dev/null
+++ b/src/zstring_view.cpp
@@ -0,0 +1,12 @@
+#include <stdplus/zstring_view.hpp>
+
+namespace stdplus
+{
+
+template class basic_zstring_view<char>;
+template class basic_zstring_view<char8_t>;
+template class basic_zstring_view<char16_t>;
+template class basic_zstring_view<char32_t>;
+template class basic_zstring_view<wchar_t>;
+
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index 452edae..ade258e 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_view': [stdplus_dep, gtest_main_dep],
}
if has_dl
diff --git a/test/zstring_view.cpp b/test/zstring_view.cpp
new file mode 100644
index 0000000..45ee8d3
--- /dev/null
+++ b/test/zstring_view.cpp
@@ -0,0 +1,65 @@
+#include <gtest/gtest.h>
+#include <iostream>
+#include <set>
+#include <stdplus/zstring_view.hpp>
+#include <string>
+#include <unordered_set>
+
+namespace stdplus
+{
+
+using std::literals::string_literals::operator""s;
+using zstring_view_literals::operator""_zsv;
+
+TEST(ZstringView, Basic)
+{
+ auto s1 = zstring_view("ac");
+ auto s = "b"s;
+ auto s2 = zstring_view(s);
+ std::string_view sv = s1;
+
+ EXPECT_NE(sv, s2);
+ EXPECT_NE(s1, s2);
+
+ EXPECT_EQ("ac"_zsv, s1);
+ EXPECT_EQ("ac", s1);
+ EXPECT_EQ(s1, "ac");
+ EXPECT_EQ(s, s2);
+ EXPECT_EQ(s2, s);
+ EXPECT_EQ(s1, sv);
+ EXPECT_EQ(sv, s1);
+ EXPECT_LT("ab", s1);
+ EXPECT_GT("ad", s1);
+ EXPECT_LE(s1, "ac");
+ EXPECT_LE(s, s2);
+ EXPECT_LE(s2, s);
+ EXPECT_LE(s1, sv);
+ EXPECT_LE(sv, s1);
+
+ std::cerr << s1;
+
+ std::unordered_set<zstring_view> uset{s1, s2};
+ EXPECT_EQ(1, uset.count("ac"));
+ EXPECT_EQ(1, uset.count("b"));
+ std::set<zstring_view> set{s2, s2};
+ EXPECT_EQ(0, set.count("ac"));
+ EXPECT_EQ(1, set.count("b"));
+}
+
+TEST(ZstringView, ConstructError)
+{
+ auto s = "hi\0"s;
+ EXPECT_THROW((zstring_view(s)), std::invalid_argument);
+}
+
+TEST(ZstringView, Suffix)
+{
+ auto s1 = zstring_view("ac");
+
+ EXPECT_EQ("ac", s1.suffix(0));
+ EXPECT_EQ("c", s1.suffix(1));
+ EXPECT_EQ("", s1.suffix(2));
+ EXPECT_THROW(s1.suffix(3), std::out_of_range);
+}
+
+} // namespace stdplus