From 125ea6583d46e862a7c45b2619e0c133996fd4bc Mon Sep 17 00:00:00 2001 From: Edward Nolan Date: Wed, 25 Mar 2026 13:21:28 -0400 Subject: [PATCH] Restore support for older compilers and C++17 Add compatibility or #ifdefs for the following features: - Three-way comparison - char8_t - std::format - std::type_identity --- .github/workflows/ci_tests.yml | 24 ++-- README.md | 11 +- examples/example.cpp | 14 +++ include/beman/cstring_view/cstring_view.hpp | 112 +++++++++++++++--- .../beman/cstring_view/cstring_view.test.cpp | 6 +- 5 files changed, 138 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index a7d8233..71e10f1 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -49,14 +49,22 @@ jobs: } ] }, - { "cxxversions": ["c++23", "c++20"], + { "cxxversions": ["c++23", "c++20", "c++17"], "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] } ] }, - { "versions": ["14"], + { "versions": ["14", "13"], "tests": [ - { "cxxversions": ["c++26", "c++23", "c++20"], + { "cxxversions": ["c++26", "c++23", "c++20", "c++17"], + "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + }, + { + "versions": ["12", "11"], + "tests": [ + { "cxxversions": ["c++23", "c++20", "c++17"], "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] } ] @@ -75,7 +83,7 @@ jobs: } ] }, - { "cxxversions": ["c++23", "c++20"], + { "cxxversions": ["c++23", "c++20", "c++17"], "tests": [ {"stdlibs": ["libstdc++", "libc++"], "tests": ["Release.Default"]} ] @@ -84,7 +92,7 @@ jobs: }, { "versions": ["21", "20", "19"], "tests": [ - { "cxxversions": ["c++26", "c++23", "c++20"], + { "cxxversions": ["c++26", "c++23", "c++20", "c++17"], "tests": [ {"stdlibs": ["libstdc++", "libc++"], "tests": ["Release.Default"]} ] @@ -93,10 +101,10 @@ jobs: }, { "versions": ["18", "17"], "tests": [ - { "cxxversions": ["c++26", "c++23", "c++20"], + { "cxxversions": ["c++26", "c++23", "c++20", "c++17"], "tests": [{"stdlibs": ["libc++"], "tests": ["Release.Default"]}] }, - { "cxxversions": ["c++20"], + { "cxxversions": ["c++20", "c++17"], "tests": [{"stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] } ] @@ -105,7 +113,7 @@ jobs: "appleclang": [ { "versions": ["latest"], "tests": [ - { "cxxversions": ["c++26", "c++23", "c++20"], + { "cxxversions": ["c++26", "c++23", "c++20", "c++17"], "tests": [{ "stdlibs": ["libc++"], "tests": ["Release.Default"]}] } ] diff --git a/README.md b/README.md index a0def8c..31b6c6f 100644 --- a/README.md +++ b/README.md @@ -52,11 +52,12 @@ You can disable building tests by setting CMake option `BEMAN_CSTRING_VIEW_BUILD | Compiler | Version | C++ Standards | Standard Library | |------------|---------|---------------|-------------------| -| GCC | 15-14 | C++26-C++20 | libstdc++ | -| Clang | 22-19 | C++26-C++20 | libstdc++, libc++ | -| Clang | 18-17 | C++26-C++20 | libc++ | -| Clang | 18-17 | C++20 | libstdc++ | -| AppleClang | latest | C++26-C++20 | libc++ | +| GCC | 15-13 | C++26-C++17 | libstdc++ | +| GCC | 12-11 | C++23-C++17 | libstdc++ | +| Clang | 22-19 | C++26-C++17 | libstdc++, libc++ | +| Clang | 18-17 | C++26-C++17 | libc++ | +| Clang | 18-17 | C++20, C++17 | libstdc++ | +| AppleClang | latest | C++26-C++17 | libc++ | | MSVC | latest | C++23 | MSVC STL | ## Development diff --git a/examples/example.cpp b/examples/example.cpp index dda6be0..a9a6efb 100644 --- a/examples/example.cpp +++ b/examples/example.cpp @@ -9,6 +9,7 @@ using namespace std::literals; using namespace beman::literals; +#if __cpp_lib_three_way_comparison std::string_view to_string(std::strong_ordering order) { if (order == std::strong_ordering::equal) { return "equal"; @@ -23,6 +24,7 @@ std::string_view to_string(std::strong_ordering order) { return "internal error"; } } +#endif int main() { std::string s = "hello world"; @@ -30,10 +32,12 @@ int main() { beman::cstring_view z1 = s; beman::cstring_view empty; std::cout << z0 << "\n"; +#if __cpp_lib_starts_ends_with >= 201711L std::cout << s.starts_with(z0) << "\n"; std::cout << z0.starts_with(s) << "\n"; std::cout << z0.starts_with("hello") << "\n"; std::cout << z0.starts_with("hello"_csv) << "\n"; +#endif std::cout << std::hash{}(z1) << "\n"; std::cout << z1 << std::endl; std::cout << ("hello"_csv == "hello"sv) << "\n"; @@ -41,7 +45,9 @@ int main() { std::cout << ("hello"_csv != "goodbye"sv) << "\n"; std::cout << ("hello"_csv != "goodbye"_csv) << "\n"; std::cout << (z0 == z1) << "\n"; +#if __cpp_lib_three_way_comparison std::cout << to_string(z0 <=> z1) << "\n"; +#endif std::cout << z0[z0.size()] * 1 << "\n"; std::cout << z0.c_str() << "\n"; std::cout << "\"" << empty << "\"\n"; @@ -51,11 +57,15 @@ int main() { beman::wcstring_view wz0 = L"hello"; beman::wcstring_view wz1 = ws; beman::wcstring_view wempty; +#if __cpp_lib_format >= 201907L std::wcout << std::format(L"{}\n", wz0); +#endif +#if __cpp_lib_starts_ends_with >= 201711L std::cout << ws.starts_with(wz0) << "\n"; std::cout << wz0.starts_with(ws) << "\n"; std::cout << wz0.starts_with(L"hello") << "\n"; std::cout << wz0.starts_with(L"hello"_csv) << "\n"; +#endif std::cout << std::hash{}(wz1) << "\n"; std::wcout << wz1 << std::endl; std::cout << (L"hello"_csv == L"hello"sv) << "\n"; @@ -63,9 +73,13 @@ int main() { std::cout << (L"hello"_csv != L"goodbye"sv) << "\n"; std::cout << (L"hello"_csv != L"goodbye"_csv) << "\n"; std::cout << (wz0 == wz1) << "\n"; +#if __cpp_lib_three_way_comparison std::cout << to_string(wz0 <=> wz1) << "\n"; +#endif std::cout << wz0[wz0.size()] * 1 << "\n"; +#if __cpp_lib_format >= 201907L std::wcout << std::format(L"{}\n", wz0.c_str()); std::wcout << std::format(L"\"{}\"\n", wempty); +#endif std::cout << (wempty == L""_csv) << "\n"; } diff --git a/include/beman/cstring_view/cstring_view.hpp b/include/beman/cstring_view/cstring_view.hpp index a698e6b..0321b7b 100644 --- a/include/beman/cstring_view/cstring_view.hpp +++ b/include/beman/cstring_view/cstring_view.hpp @@ -5,8 +5,12 @@ #include #include -#include -#include +#if __has_include() + #include +#endif +#if __has_include() + #include +#endif #include #include #include @@ -14,6 +18,18 @@ namespace beman { +namespace detail { + +template +struct type_identity { + using type = T; +}; + +template +using type_identity_t = typename type_identity::type; + +} // namespace detail + // [cstring.view.template], class template basic_cstring_view template > class basic_cstring_view; // partially freestanding @@ -29,12 +45,30 @@ namespace ranges { */ // [cstring.view.comparison], non-member comparison functions template -constexpr bool operator==(basic_cstring_view x, - std::type_identity_t> y) noexcept; +constexpr bool operator==(basic_cstring_view x, + detail::type_identity_t> y) noexcept; +#if __cpp_lib_three_way_comparison +template +constexpr auto operator<=>(basic_cstring_view x, + detail::type_identity_t> y) noexcept; +#else template -constexpr auto operator<=>(basic_cstring_view x, - std::type_identity_t> y) noexcept; +constexpr bool operator!=(basic_cstring_view x, + detail::type_identity_t> y) noexcept; +template +constexpr bool operator<(basic_cstring_view x, + detail::type_identity_t> y) noexcept; +template +constexpr bool operator>(basic_cstring_view x, + detail::type_identity_t> y) noexcept; +template +constexpr bool operator<=(basic_cstring_view x, + detail::type_identity_t> y) noexcept; +template +constexpr bool operator>=(basic_cstring_view x, + detail::type_identity_t> y) noexcept; +#endif // [cstring.view.io], inserters and extractors template @@ -42,8 +76,10 @@ std::basic_ostream& operator<<(std::basic_ostream& basic_cstring_view str); // basic_cstring_view typedef-names -using cstring_view = basic_cstring_view; -using u8cstring_view = basic_cstring_view; +using cstring_view = basic_cstring_view; +#if __cpp_char8_t >= 201811L +using u8cstring_view = basic_cstring_view; +#endif using u16cstring_view = basic_cstring_view; using u32cstring_view = basic_cstring_view; using wcstring_view = basic_cstring_view; @@ -53,8 +89,10 @@ template struct hash; template <> struct hash; +#if __cpp_char8_t >= 201811L template <> struct hash; +#endif template <> struct hash; template <> @@ -76,8 +114,10 @@ inline namespace cstring_view_literals { #pragma warning(disable : 4455) #endif // [cstring.view.literals], suffix for basic_cstring_view literals -constexpr cstring_view operator"" csv(const char* str, size_t len) noexcept; -constexpr u8cstring_view operator"" csv(const char8_t* str, size_t len) noexcept; +constexpr cstring_view operator"" csv(const char* str, size_t len) noexcept; +#if __cpp_char8_t >= 201811L +constexpr u8cstring_view operator"" csv(const char8_t* str, size_t len) noexcept; +#endif constexpr u16cstring_view operator"" csv(const char16_t* str, size_t len) noexcept; constexpr u32cstring_view operator"" csv(const char32_t* str, size_t len) noexcept; constexpr wcstring_view operator"" csv(const wchar_t* str, size_t len) noexcept; @@ -153,7 +193,12 @@ class basic_cstring_view { } constexpr const_reference at(size_type pos) const { if (pos > size_) { +#if __cpp_lib_format >= 201907L throw std::out_of_range(std::format("basic_cstring_view::at: pos ({}) > size() {}", pos, size_)); +#else + throw std::out_of_range(std::string{"basic_cstring_view::at: pos ("} + std::to_string(pos) + + std::string{") > size() "} + std::to_string(size_)); +#endif } return data_[pos]; } @@ -210,6 +255,7 @@ class basic_cstring_view { return std::basic_string_view(*this).compare(pos1, n1, s, n2); } +#if __cpp_lib_starts_ends_with >= 201711L constexpr bool starts_with(std::basic_string_view x) const noexcept { return std::basic_string_view(*this).starts_with(x); } @@ -228,6 +274,7 @@ class basic_cstring_view { constexpr bool ends_with(const charT* x) const { return std::basic_string_view(*this).ends_with(x); } +#endif constexpr bool contains(std::basic_string_view x) const noexcept { return std::basic_string_view(*this).contains(x); @@ -321,10 +368,12 @@ class basic_cstring_view { inline namespace literals { inline namespace cstring_view_literals { // [cstring.view.literals], suffix for basic_cstring_view literals -constexpr cstring_view operator""_csv(const char* str, size_t len) noexcept { return basic_cstring_view(str, len); } +constexpr cstring_view operator""_csv(const char* str, size_t len) noexcept { return basic_cstring_view(str, len); } +#if __cpp_char8_t >= 201811L constexpr u8cstring_view operator""_csv(const char8_t* str, size_t len) noexcept { return basic_cstring_view(str, len); } +#endif constexpr u16cstring_view operator""_csv(const char16_t* str, size_t len) noexcept { return basic_cstring_view(str, len); } @@ -338,16 +387,45 @@ constexpr wcstring_view operator""_csv(const wchar_t* str, size_t len) noexcept } // namespace literals template -constexpr bool operator==(basic_cstring_view x, - std::type_identity_t> y) noexcept { +constexpr bool operator==(basic_cstring_view x, + detail::type_identity_t> y) noexcept { return std::basic_string_view(x) == std::basic_string_view(y); } +#if __cpp_lib_three_way_comparison template -constexpr auto operator<=>(basic_cstring_view x, - std::type_identity_t> y) noexcept { +constexpr auto operator<=>(basic_cstring_view x, + detail::type_identity_t> y) noexcept { return std::basic_string_view(x) <=> std::basic_string_view(y); } +#else +template +constexpr bool operator!=(basic_cstring_view x, + detail::type_identity_t> y) noexcept { + return std::basic_string_view(x) != std::basic_string_view(y); +} +// Definitions +template +constexpr bool operator<(basic_cstring_view x, + detail::type_identity_t> y) noexcept { + return std::basic_string_view(x) < std::basic_string_view(y); +} +template +constexpr bool operator>(basic_cstring_view x, + detail::type_identity_t> y) noexcept { + return std::basic_string_view(x) > std::basic_string_view(y); +} +template +constexpr bool operator<=(basic_cstring_view x, + detail::type_identity_t> y) noexcept { + return std::basic_string_view(x) <= std::basic_string_view(y); +} +template +constexpr bool operator>=(basic_cstring_view x, + detail::type_identity_t> y) noexcept { + return std::basic_string_view(x) >= std::basic_string_view(y); +} +#endif template std::basic_ostream& operator<<(std::basic_ostream& os, @@ -357,6 +435,7 @@ std::basic_ostream& operator<<(std::basic_ostream& } // namespace beman +#if __cpp_lib_format >= 201907L // [format.formatter.spec] template struct std::formatter, charT> { @@ -370,15 +449,18 @@ struct std::formatter, charT> { private: formatter, charT> sv_formatter; }; +#endif template <> struct std::hash { auto operator()(const beman::cstring_view& sv) const noexcept { return std::hash{}(sv); } }; +#if __cpp_char8_t >= 201811L template <> struct std::hash { auto operator()(const beman::u8cstring_view& sv) const noexcept { return std::hash{}(sv); } }; +#endif template <> struct std::hash { auto operator()(const beman::u16cstring_view& sv) const noexcept { return std::hash{}(sv); } diff --git a/tests/beman/cstring_view/cstring_view.test.cpp b/tests/beman/cstring_view/cstring_view.test.cpp index 73c13df..cba0ebd 100644 --- a/tests/beman/cstring_view/cstring_view.test.cpp +++ b/tests/beman/cstring_view/cstring_view.test.cpp @@ -16,12 +16,16 @@ TEST(StringView, ConstructionDestruction) { EXPECT_EQ(h1.c_str(), h2.c_str()); EXPECT_NE(h1.c_str(), s.c_str()); EXPECT_TRUE(h1 == h2); - EXPECT_TRUE(s == h1); + EXPECT_TRUE(h1 == s); +#if __cpp_lib_starts_ends_with >= 201711L EXPECT_TRUE(h1.starts_with("he")); EXPECT_TRUE(h1.ends_with("lo")); +#endif EXPECT_TRUE(h1 == "hello"); EXPECT_TRUE(h1 == "hello"sv); +#if __cpp_lib_three_way_comparison EXPECT_EQ(h1 <=> h1, std::strong_ordering::equal); +#endif EXPECT_EQ(h1[0], 'h'); auto first = h1.substr(0, 2); auto end = h1.substr(2);