From 430a3dbec429f3f16190a37bcd46c3aef3e0e2ba Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Sat, 23 Sep 2023 16:40:20 -0700 Subject: [PATCH 01/25] Take FixedString out of detail namespace --- include/fixed_containers/fixed_string.hpp | 9 ++++----- test/fixed_string_test.cpp | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index 2853056b..ed7e1174 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -17,8 +17,7 @@ template concept FixedStringChecking = requires(std::size_t i, std::size_t s, const StringLiteral& error_message, - const std_transition::source_location& loc) -{ + const std_transition::source_location& loc) { T::out_of_range(i, s, loc); // ~ std::out_of_range }; @@ -34,7 +33,7 @@ struct AbortChecking }; } // namespace fixed_containers::fixed_string_customize -namespace fixed_containers::fixed_string_detail +namespace fixed_containers { template -struct tuple_size> +struct tuple_size> : std::integral_constant { static_assert(fixed_containers::AlwaysFalseV, diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 50e98134..c4158f08 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -5,7 +5,7 @@ #include -namespace fixed_containers::fixed_string_detail +namespace fixed_containers { namespace { @@ -192,4 +192,4 @@ TEST(FixedString, Data) } } -} // namespace fixed_containers::fixed_string_detail +} // namespace fixed_containers From 7008530df3ee226579bb9e051933ee0bc52e309f Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Sun, 24 Sep 2023 13:00:25 -0700 Subject: [PATCH 02/25] Add FixedString to README.md --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index f770753c..66556130 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Header-only C++20 library that provides containers with the following properties * `EnumMap`/`EnumSet` - For enum keys only, Map/Set implementation with `std::map`/`std::set` API and "fixed container" properties. O(1) lookups. * `FixedDeque` - Deque implementation with `std::deque` API and "fixed container" properties * `FixedStack` - Stack implementation with `std::stack` API and "fixed container" properties +* `FixedString` - String implementation with `std::string` API and "fixed container" properties * `StringLiteral` - Compile-time null-terminated literal string. * Rich enums - `enum` & `class` hybrid. @@ -170,6 +171,22 @@ More examples can be found [here](test/enums_test_common.hpp). static_assert(s1.size() == 2); ``` +- FixedString + ```C++ + constexpr auto v1 = []() + { + FixedString<11> v{"012"}; + v.at(1) = 'b'; + + return v; + }(); + + static_assert(v1.at(0) == '0'); + static_assert(v1.at(1) == 'b'); + static_assert(v1.at(2) == '2'); + static_assert(v1.size() == 3); + ``` + - StringLiteral ```C++ static constexpr const char* s = "blah"; // strlen==4, sizeof==8 From 01aa0892deceb0a5222d664625e4f7696d444d00 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Mon, 25 Sep 2023 20:14:12 -0700 Subject: [PATCH 03/25] [FixedString] Switch to FixedVector for underlying data --- BUILD.bazel | 1 + include/fixed_containers/fixed_string.hpp | 124 ++++++++++++---------- test/fixed_string_test.cpp | 4 + 3 files changed, 75 insertions(+), 54 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index dc0e8fe7..28638ada 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -242,6 +242,7 @@ cc_library( includes = ["include"], deps = [ ":concepts", + ":fixed_vector", ":preconditions", ":source_location", ":string_literal", diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index ed7e1174..36f0e6b9 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -1,6 +1,7 @@ #pragma once #include "fixed_containers/concepts.hpp" +#include "fixed_containers/fixed_vector.hpp" #include "fixed_containers/preconditions.hpp" #include "fixed_containers/source_location.hpp" #include "fixed_containers/string_literal.hpp" @@ -14,12 +15,7 @@ namespace fixed_containers::fixed_string_customize { template -concept FixedStringChecking = requires(std::size_t i, - std::size_t s, - const StringLiteral& error_message, - const std_transition::source_location& loc) { - T::out_of_range(i, s, loc); // ~ std::out_of_range -}; +concept FixedStringChecking = fixed_vector_customize::FixedVectorChecking; template struct AbortChecking @@ -30,6 +26,25 @@ struct AbortChecking { std::abort(); } + + [[noreturn]] static void length_error(const std::size_t /*target_capacity*/, + const std_transition::source_location& /*loc*/) + { + std::abort(); + } + + [[noreturn]] static constexpr void empty_container_access( + const std_transition::source_location& /*loc*/) + { + std::abort(); + } + + [[noreturn]] static constexpr void invalid_argument( + const fixed_containers::StringLiteral& /*error_message*/, + const std_transition::source_location& /*loc*/) + { + std::abort(); + } }; } // namespace fixed_containers::fixed_string_customize @@ -41,89 +56,70 @@ template ; public: - using value_type = char; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using pointer = char*; - using const_pointer = const char*; - using reference = char&; - using const_reference = const char&; + using value_type = typename FixedVecStorage::value_type; + using size_type = typename FixedVecStorage::size_type; + using difference_type = typename FixedVecStorage::difference_type; + using pointer = typename FixedVecStorage::pointer; + using const_pointer = typename FixedVecStorage::const_pointer; + using reference = typename FixedVecStorage::reference; + using const_reference = typename FixedVecStorage::const_reference; public: // Public so this type is a structural type and can thus be used in template parameters - std::size_t IMPLEMENTATION_DETAIL_DO_NOT_USE_length_; - std::array IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; + FixedVecStorage IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; public: - constexpr FixedString() noexcept - : IMPLEMENTATION_DETAIL_DO_NOT_USE_length_{} - , IMPLEMENTATION_DETAIL_DO_NOT_USE_data_{} + constexpr FixedString(const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + : IMPLEMENTATION_DETAIL_DO_NOT_USE_data_{} { - null_terminate(); + null_terminate_at_max_length(); + null_terminate(loc); } - explicit(false) constexpr FixedString(const std::string_view& view) noexcept - : FixedString{} + explicit(false) constexpr FixedString(const std::string_view& view, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + : IMPLEMENTATION_DETAIL_DO_NOT_USE_data_{view.begin(), view.end(), loc} { - assert(view.size() <= MAXIMUM_LENGTH); - for (std::size_t i = 0; i < view.size(); i++) - { - IMPLEMENTATION_DETAIL_DO_NOT_USE_data_[i] = view[i]; - } - - IMPLEMENTATION_DETAIL_DO_NOT_USE_length_ = view.size(); - null_terminate(); + null_terminate_at_max_length(); + null_terminate(loc); } [[nodiscard]] constexpr reference operator[](size_type i) noexcept { // Cannot capture real source_location for operator[] // This operator should not range-check according to the spec, but we want the extra safety. - return at(i, std_transition::source_location::current()); + return vec().at(i, std_transition::source_location::current()); } [[nodiscard]] constexpr const_reference operator[](size_type i) const noexcept { // Cannot capture real source_location for operator[] // This operator should not range-check according to the spec, but we want the extra safety. - return at(i, std_transition::source_location::current()); + return vec().at(i, std_transition::source_location::current()); } [[nodiscard]] constexpr reference at(size_type i, const std_transition::source_location& loc = std_transition::source_location::current()) noexcept { - if (preconditions::test(i < length())) - { - Checking::out_of_range(i, length(), loc); - } - return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_.at(i); + return vec().at(i, loc); } [[nodiscard]] constexpr const_reference at( size_type i, const std_transition::source_location& loc = std_transition::source_location::current()) const noexcept { - if (preconditions::test(i < length())) - { - Checking::out_of_range(i, length(), loc); - } - return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_.at(i); + return vec().at(i, loc); } - [[nodiscard]] constexpr const char* data() const noexcept - { - return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_.data(); - } - [[nodiscard]] constexpr char* data() noexcept - { - return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_.data(); - } + [[nodiscard]] constexpr const char* data() const noexcept { return vec().data(); } + [[nodiscard]] constexpr char* data() noexcept { return vec().data(); } [[nodiscard]] constexpr bool empty() const noexcept { return length() == 0; } - [[nodiscard]] constexpr std::size_t length() const noexcept - { - return IMPLEMENTATION_DETAIL_DO_NOT_USE_length_; - } + [[nodiscard]] constexpr std::size_t length() const noexcept { return vec().size(); } [[nodiscard]] constexpr std::size_t size() const noexcept { return length(); } [[nodiscard]] constexpr std::size_t max_size() const noexcept { return MAXIMUM_LENGTH; } [[nodiscard]] constexpr std::size_t capacity() const noexcept { return max_size(); } @@ -134,7 +130,27 @@ class FixedString } private: - constexpr void null_terminate() { IMPLEMENTATION_DETAIL_DO_NOT_USE_data_.at(length()) = '\0'; } + constexpr void unchecked_null_terminate(std::size_t n) + { + // This bypasses the vector's bounds check + // This keeps the vector's size equal to the length of the string + *std::next(vec().data(), static_cast(n)) = '\0'; + } + + constexpr void null_terminate_at_max_length() { unchecked_null_terminate(MAXIMUM_LENGTH); } + constexpr void null_terminate(const std_transition::source_location& loc) + { + const std::size_t n = length(); + if (preconditions::test(n <= MAXIMUM_LENGTH)) + { + Checking::length_error(n + 1, loc); + } + + unchecked_null_terminate(length()); + } + + constexpr const FixedVecStorage& vec() const { return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; } + constexpr FixedVecStorage& vec() { return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; } }; } // namespace fixed_containers diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index c4158f08..6701fba1 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -171,10 +171,14 @@ TEST(FixedString, Data) static_assert(*std::next(v1.data(), 0) == '0'); static_assert(*std::next(v1.data(), 1) == '1'); static_assert(*std::next(v1.data(), 2) == '2'); + static_assert(*std::next(v1.data(), 3) == '\0'); + static_assert(*std::next(v1.data(), 8) == '\0'); EXPECT_EQ(*std::next(v1.data(), 0), '0'); EXPECT_EQ(*std::next(v1.data(), 1), '1'); EXPECT_EQ(*std::next(v1.data(), 2), '2'); + EXPECT_EQ(*std::next(v1.data(), 3), '\0'); + EXPECT_EQ(*std::next(v1.data(), 8), '\0'); static_assert(v1.size() == 3); } From daf3c318674e1dfe712ac4a0ec14652e10e82e38 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Mon, 25 Sep 2023 22:27:37 -0700 Subject: [PATCH 04/25] [FixedString] Implement constructors, iterators, push_back(), erase(), equality --- BUILD.bazel | 1 + include/fixed_containers/fixed_string.hpp | 103 +++++- test/fixed_string_test.cpp | 405 ++++++++++++++++++++++ 3 files changed, 505 insertions(+), 4 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index 28638ada..7cdb032b 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -693,6 +693,7 @@ cc_test( ":concepts", ":consteval_compare", ":fixed_string", + ":test_utilities_common", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index 36f0e6b9..9da4f33a 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -57,8 +57,24 @@ class FixedString { using Checking = CheckingType; using CharT = char; + using Self = FixedString; using FixedVecStorage = FixedVector; + struct ScopedNullTermination + { + Self* self_; + std_transition::source_location loc_; + + constexpr ScopedNullTermination(Self* self, + const std_transition::source_location& loc) noexcept + : self_(self) + , loc_(loc) + { + } + + constexpr ~ScopedNullTermination() noexcept { self_->null_terminate(loc_); } + }; + public: using value_type = typename FixedVecStorage::value_type; using size_type = typename FixedVecStorage::size_type; @@ -67,6 +83,10 @@ class FixedString using const_pointer = typename FixedVecStorage::const_pointer; using reference = typename FixedVecStorage::reference; using const_reference = typename FixedVecStorage::const_reference; + using const_iterator = typename FixedVecStorage::const_iterator; + using iterator = typename FixedVecStorage::iterator; + using reverse_iterator = typename FixedVecStorage::reverse_iterator; + using const_reverse_iterator = typename FixedVecStorage::const_reverse_iterator; public: // Public so this type is a structural type and can thus be used in template parameters FixedVecStorage IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; @@ -80,6 +100,32 @@ class FixedString null_terminate(loc); } + constexpr FixedString( + size_type count, + CharT ch, + const std_transition::source_location& loc = std_transition::source_location::current()) + : IMPLEMENTATION_DETAIL_DO_NOT_USE_data_{count, ch, loc} + { + null_terminate_at_max_length(); + null_terminate(loc); + } + + constexpr FixedString( + const CharT* s, + const std_transition::source_location& loc = std_transition::source_location::current()) + : FixedString(std::string_view{s}, loc) + { + } + + constexpr FixedString( + std::initializer_list ilist, + const std_transition::source_location& loc = std_transition::source_location::current()) + : IMPLEMENTATION_DETAIL_DO_NOT_USE_data_{ilist, loc} + { + null_terminate_at_max_length(); + null_terminate(loc); + } + explicit(false) constexpr FixedString(const std::string_view& view, const std_transition::source_location& loc = std_transition::source_location::current()) noexcept @@ -118,26 +164,74 @@ class FixedString [[nodiscard]] constexpr const char* data() const noexcept { return vec().data(); } [[nodiscard]] constexpr char* data() noexcept { return vec().data(); } + + constexpr iterator begin() noexcept { return vec().begin(); } + constexpr const_iterator begin() const noexcept { return cbegin(); } + constexpr const_iterator cbegin() const noexcept { return vec().cbegin(); } + constexpr iterator end() noexcept { return vec().end(); } + constexpr const_iterator end() const noexcept { return cend(); } + constexpr const_iterator cend() const noexcept { return vec().cend(); } + + constexpr reverse_iterator rbegin() noexcept { return vec().rbegin(); } + constexpr const_reverse_iterator rbegin() const noexcept { return crbegin(); } + constexpr const_reverse_iterator crbegin() const noexcept { return vec().crbegin(); } + constexpr reverse_iterator rend() noexcept { return vec().rend(); } + constexpr const_reverse_iterator rend() const noexcept { return crend(); } + constexpr const_reverse_iterator crend() const noexcept { return vec().crend(); } + [[nodiscard]] constexpr bool empty() const noexcept { return length() == 0; } [[nodiscard]] constexpr std::size_t length() const noexcept { return vec().size(); } [[nodiscard]] constexpr std::size_t size() const noexcept { return length(); } [[nodiscard]] constexpr std::size_t max_size() const noexcept { return MAXIMUM_LENGTH; } [[nodiscard]] constexpr std::size_t capacity() const noexcept { return max_size(); } + constexpr iterator erase( + const_iterator position, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + ScopedNullTermination guard{this, loc}; + return vec().erase(position, loc); + } + constexpr iterator erase( + const_iterator first, + const_iterator last, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + ScopedNullTermination guard{this, loc}; + return vec().erase(first, last, loc); + } + + constexpr void push_back( + CharT ch, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + vec().push_back(ch, loc); + null_terminate(loc); + } + explicit(false) constexpr operator std::string_view() const { return std::string_view(data(), length()); } + template + constexpr bool operator==(const FixedString& other) const + { + return vec() == other.IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; + } + constexpr bool operator==(const CharT* other) const + { + return static_cast(*this) == std::string_view{other}; + } + private: - constexpr void unchecked_null_terminate(std::size_t n) + constexpr void null_terminate(std::size_t n) { // This bypasses the vector's bounds check // This keeps the vector's size equal to the length of the string *std::next(vec().data(), static_cast(n)) = '\0'; } - - constexpr void null_terminate_at_max_length() { unchecked_null_terminate(MAXIMUM_LENGTH); } constexpr void null_terminate(const std_transition::source_location& loc) { const std::size_t n = length(); @@ -146,8 +240,9 @@ class FixedString Checking::length_error(n + 1, loc); } - unchecked_null_terminate(length()); + null_terminate(length()); } + constexpr void null_terminate_at_max_length() { null_terminate(MAXIMUM_LENGTH); } constexpr const FixedVecStorage& vec() const { return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; } constexpr FixedVecStorage& vec() { return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; } diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 6701fba1..b34c17be 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -1,10 +1,15 @@ #include "fixed_containers/fixed_string.hpp" +#include "test_utilities_common.hpp" + #include "fixed_containers/concepts.hpp" #include "fixed_containers/consteval_compare.hpp" #include +#include +#include + namespace fixed_containers { namespace @@ -15,6 +20,11 @@ static_assert(TriviallyCopyable); static_assert(NotTrivial); static_assert(StandardLayout); static_assert(IsStructuralType); + +void const_ref(const int&) {} +void const_span_ref(const std::span&) {} +void const_span_of_const_ref(const std::span&) {} + } // namespace TEST(FixedString, DefaultConstructor) @@ -24,6 +34,42 @@ TEST(FixedString, DefaultConstructor) static_assert(v1.max_size() == 8); } +TEST(FixedString, CountConstructor) +{ + { + constexpr FixedString<8> v2(5, '3'); + static_assert(v2.size() == 5); + static_assert(v2.max_size() == 8); + static_assert(v2 == "33333"); + } +} + +TEST(FixedString, ConstCharPointerConstructor) +{ + { + constexpr FixedString<8> v2("12345"); + static_assert(v2.size() == 5); + static_assert(v2.max_size() == 8); + static_assert(v2 == "12345"); + } +} + +TEST(FixedString, InitializerConstructor) +{ + constexpr FixedString<3> v1{'7', '9'}; + static_assert(v1[0] == '7'); + static_assert(v1[1] == '9'); + static_assert(v1.size() == 2); + + constexpr FixedString<3> v2{{'6', '5'}}; + static_assert(v2[0] == '6'); + static_assert(v2[1] == '5'); + static_assert(v2.size() == 2); + + EXPECT_EQ(v1, "79"); + EXPECT_EQ(v2, "65"); +} + TEST(FixedString, StringViewConstructor) { constexpr std::string_view STRING_VIEW{"123456789"}; @@ -107,6 +153,224 @@ TEST(FixedString, At_OutOfBounds) EXPECT_DEATH(static_cast(v3.at(v2.size())), ""); } +TEST(FixedString, IteratorAssignment) +{ + FixedString<8>::iterator it; // Default construction + FixedString<8>::const_iterator const_it; // Default construction + + const_it = it; // Non-const needs to be assignable to const +} + +TEST(FixedString, TrivialIterators) +{ + { + constexpr FixedString<3> v1{{'7', '8', '9'}}; + + static_assert(std::distance(v1.cbegin(), v1.cend()) == 3); + + static_assert(*v1.begin() == '7'); + static_assert(*std::next(v1.begin(), 1) == '8'); + static_assert(*std::next(v1.begin(), 2) == '9'); + + static_assert(*std::prev(v1.end(), 1) == '9'); + static_assert(*std::prev(v1.end(), 2) == '8'); + static_assert(*std::prev(v1.end(), 3) == '7'); + } + + { + /*non-const*/ FixedString<8> v{}; + v.push_back('0'); + v.push_back('1'); + v.push_back('2'); + v.push_back('3'); + { + char ctr = '0'; + for (auto it = v.begin(); it != v.end(); it++) + { + EXPECT_LT(ctr, '4'); + EXPECT_EQ(ctr, *it); + ++ctr; + } + EXPECT_EQ(ctr, '4'); + } + { + char ctr = '0'; + for (auto it = v.cbegin(); it != v.cend(); it++) + { + EXPECT_LT(ctr, '4'); + EXPECT_EQ(ctr, *it); + ++ctr; + } + EXPECT_EQ(ctr, '4'); + } + } + { + const FixedString<8> v = {"0123"}; + { + char ctr = '0'; + for (auto it = v.begin(); it != v.end(); it++) + { + EXPECT_LT(ctr, '4'); + EXPECT_EQ(ctr, *it); + ++ctr; + } + EXPECT_EQ(ctr, '4'); + } + { + char ctr = '0'; + for (auto it = v.cbegin(); it != v.cend(); it++) + { + EXPECT_LT(ctr, '4'); + EXPECT_EQ(ctr, *it); + ++ctr; + } + EXPECT_EQ(ctr, '4'); + } + } +} + +TEST(FixedString, ReverseIterators) +{ + { + constexpr FixedString<3> v1{{'7', '8', '9'}}; + + static_assert(std::distance(v1.crbegin(), v1.crend()) == 3); + + static_assert(*v1.rbegin() == '9'); + static_assert(*std::next(v1.rbegin(), 1) == '8'); + static_assert(*std::next(v1.rbegin(), 2) == '7'); + + static_assert(*std::prev(v1.rend(), 1) == '7'); + static_assert(*std::prev(v1.rend(), 2) == '8'); + static_assert(*std::prev(v1.rend(), 3) == '9'); + } + + { + /*non-cost*/ FixedString<8> v{}; + v.push_back(0); + v.push_back(1); + v.push_back(2); + v.push_back(3); + { + int ctr = 3; + for (auto it = v.rbegin(); it != v.rend(); it++) + { + EXPECT_GT(ctr, -1); + EXPECT_EQ(ctr, *it); + --ctr; + } + EXPECT_EQ(ctr, -1); + } + { + int ctr = 3; + for (auto it = v.crbegin(); it != v.crend(); it++) + { + EXPECT_GT(ctr, -1); + EXPECT_EQ(ctr, *it); + --ctr; + } + EXPECT_EQ(ctr, -1); + } + } + { + const FixedString<8> v = {"0123"}; + { + char ctr = '3'; + for (auto it = v.rbegin(); it != v.rend(); it++) + { + EXPECT_GT(ctr, '0' - 1); + EXPECT_EQ(ctr, *it); + --ctr; + } + EXPECT_EQ(ctr, '0' - 1); + } + { + char ctr = '3'; + for (auto it = v.crbegin(); it != v.crend(); it++) + { + EXPECT_GT(ctr, '0' - 1); + EXPECT_EQ(ctr, *it); + --ctr; + } + EXPECT_EQ(ctr, '0' - 1); + } + } +} + +TEST(FixedString, ReverseIteratorBase) +{ + constexpr auto v1 = []() + { + FixedString<7> v{"123"}; + auto it = v.rbegin(); // points to 3 + std::advance(it, 1); // points to 2 + // https://stackoverflow.com/questions/1830158/how-to-call-erase-with-a-reverse-iterator + v.erase(std::next(it).base()); + return v; + }(); + + static_assert(v1 == "13"); +} + +TEST(FixedString, IterationBasic) +{ + FixedString<13> v_expected{}; + + FixedString<8> v{}; + v.push_back('0'); + v.push_back('1'); + v.push_back('2'); + v.push_back('3'); + // Expect {0, 1, 2, 3} + + char ctr = '0'; + for (const char& x : v) + { + EXPECT_LT(ctr, '4'); + EXPECT_EQ(ctr, x); + ++ctr; + } + EXPECT_EQ(ctr, '4'); + + v_expected = {"0123"}; + EXPECT_TRUE((v == v_expected)); + + v.push_back('4'); + v.push_back('5'); + + v_expected = "012345"; + EXPECT_TRUE((v == v_expected)); + + ctr = '0'; + for (const char& x : v) + { + EXPECT_LT(ctr, '6'); + EXPECT_EQ(ctr, x); + ++ctr; + } + EXPECT_EQ(ctr, '6'); + + v.erase(v.begin() + 5); + v.erase(v.begin() + 3); + v.erase(v.begin() + 1); + + v_expected = "024"; + EXPECT_TRUE((v == v_expected)); + + ctr = '0'; + for (const char& x : v) + { + EXPECT_LT(ctr, '6'); + EXPECT_EQ(ctr, x); + ctr += 2; + } + EXPECT_EQ(ctr, '6'); + + const_ref(v[0]); + const_span_ref(v); + const_span_of_const_ref(v); +} + TEST(FixedString, CapacityAndMaxSize) { { @@ -147,6 +411,111 @@ TEST(FixedString, Empty) static_assert(v1.max_size() == 7); } +TEST(FixedString, EraseRange) +{ + constexpr auto v1 = []() + { + FixedString<8> v{"012345"}; + v.erase(v.cbegin() + 2, v.begin() + 4); + return v; + }(); + + static_assert(v1 == "0145"); + static_assert(v1.size() == 4); + static_assert(v1.max_size() == 8); + + FixedString<8> v2{"214503"}; + + auto it = v2.erase(v2.begin() + 1, v2.cbegin() + 3); + EXPECT_EQ(it, v2.begin() + 1); + EXPECT_EQ(*it, '5'); + EXPECT_EQ(v2, "2503"); +} + +TEST(FixedString, EraseOne) +{ + constexpr auto v1 = []() + { + FixedString<8> v{"012345"}; + v.erase(v.cbegin()); + v.erase(v.begin() + 2); + return v; + }(); + + static_assert(v1 == "1245"); + static_assert(v1.size() == 4); + static_assert(v1.max_size() == 8); + + FixedString<8> v2{"214503"}; + + auto it = v2.erase(v2.begin()); + EXPECT_EQ(it, v2.begin()); + EXPECT_EQ(*it, '1'); + EXPECT_EQ(v2, "14503"); + it += 2; + it = v2.erase(it); + EXPECT_EQ(it, v2.begin() + 2); + EXPECT_EQ(*it, '0'); + EXPECT_EQ(v2, "1403"); + ++it; + it = v2.erase(it); + EXPECT_EQ(it, v2.cend()); + // EXPECT_EQ(*it, '\0'); // Not dereferenceable + EXPECT_EQ(v2, "140"); +} + +TEST(FixedString, Erase_Empty) +{ + { + FixedString<3> v1{}; + + // Don't Expect Death + v1.erase(std::remove_if(v1.begin(), v1.end(), [&](const auto&) { return true; }), v1.end()); + + EXPECT_DEATH(v1.erase(v1.begin()), ""); + } + + { + std::string v1{}; + + // Don't Expect Death + v1.erase(std::remove_if(v1.begin(), v1.end(), [&](const auto&) { return true; }), v1.end()); + + // The iterator pos must be valid and dereferenceable. Thus the end() iterator (which is + // valid, but is not dereferenceable) cannot be used as a value for pos. + // The behavior is undefined if iterator is not dereferenceable. + // https://en.cppreference.com/w/cpp/string/basic_string/erase + // EXPECT_DEATH(v1.erase(v1.begin()), ""); + } +} + +TEST(FixedString, PushBack) +{ + constexpr auto v1 = []() + { + FixedString<11> v{}; + v.push_back('0'); + const char value = '1'; + v.push_back(value); + v.push_back('2'); + return v; + }(); + + static_assert(v1[0] == '0'); + static_assert(v1[1] == '1'); + static_assert(v1[2] == '2'); + static_assert(v1.size() == 3); +} + +TEST(FixedString, PushBack_ExceedsCapacity) +{ + FixedString<2> v{}; + v.push_back('0'); + const char value = '1'; + v.push_back(value); + EXPECT_DEATH(v.push_back('2'), ""); +} + TEST(FixedString, StringViewConversion) { auto function_that_takes_string_view = [](const std::string_view&) {}; @@ -196,4 +565,40 @@ TEST(FixedString, Data) } } +TEST(FixedString, Equality) +{ + constexpr auto v1 = FixedString<12>{"012"}; + // Capacity difference should not affect equality + constexpr auto v2 = FixedString<11>{"012"}; + constexpr auto v3 = FixedString<12>{"092"}; + constexpr auto v4 = FixedString<12>{"01"}; + constexpr auto v5 = FixedString<12>{"012345"}; + + static_assert(v1 == v1); + static_assert(v1 == v2); + static_assert(v1 != v3); + static_assert(v1 != v4); + static_assert(v1 != v5); + + EXPECT_EQ(v1, v1); + EXPECT_EQ(v1, v2); + EXPECT_NE(v1, v3); + EXPECT_NE(v1, v4); + EXPECT_NE(v1, v5); + + const_ref(v1[0]); + const_ref(v2[0]); + const_span_of_const_ref(v1); + const_span_of_const_ref(v2); +} + +TEST(FixedString, Equality_ConstCharPointer) +{ + static_assert(FixedString<11>{"012"} == "012"); + static_assert("012" == FixedString<11>{"012"}); + + static_assert(FixedString<11>{"012"} != "0123"); + static_assert("0123" != FixedString<11>{"012"}); +} + } // namespace fixed_containers From abbb378238a503e610bde7b834481f636c26d67b Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 13:11:58 -0700 Subject: [PATCH 05/25] [Vector/Deque<->String consistency] PushBaback_Exceeds and EraseOne fix --- test/fixed_deque_test.cpp | 11 ++++++++++- test/fixed_vector_test.cpp | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/test/fixed_deque_test.cpp b/test/fixed_deque_test.cpp index e1c73b7f..2b2f6bd3 100644 --- a/test/fixed_deque_test.cpp +++ b/test/fixed_deque_test.cpp @@ -199,6 +199,15 @@ TEST(FixedDeque, PushBack) run_test(FixedDequeInitialStateLastIndex{}); } +TEST(FixedDeque, PushBack_ExceedsCapacity) +{ + FixedDeque v{}; + v.push_back(0); + const char value = 1; + v.push_back(value); + EXPECT_DEATH(v.push_back(2), ""); +} + TEST(FixedDeque, EmplaceBack) { auto run_test = [](Factory&&) @@ -1561,7 +1570,7 @@ TEST(FixedDeque, EraseOne) ++it; it = v2.erase(it); EXPECT_EQ(it, v2.cend()); - EXPECT_EQ(*it, 3); + // EXPECT_EQ(*it, 3); // Not dereferenceable EXPECT_TRUE(std::ranges::equal(v2, std::array{{1, 4, 0}})); }; diff --git a/test/fixed_vector_test.cpp b/test/fixed_vector_test.cpp index 6b2d60b5..41911542 100644 --- a/test/fixed_vector_test.cpp +++ b/test/fixed_vector_test.cpp @@ -496,6 +496,15 @@ TEST(FixedVector, PushBack) static_assert(v2.size() == 1); } +TEST(FixedVector, PushBack_ExceedsCapacity) +{ + FixedVector v{}; + v.push_back(0); + const char value = 1; + v.push_back(value); + EXPECT_DEATH(v.push_back(2), ""); +} + TEST(FixedVector, EmplaceBack) { { @@ -1606,7 +1615,7 @@ TEST(FixedVector, EraseOne) ++it; it = v2.erase(it); EXPECT_EQ(it, v2.cend()); - EXPECT_EQ(*it, 3); + // EXPECT_EQ(*it, 3); // Not dereferenceable EXPECT_TRUE(std::ranges::equal(v2, std::array{{1, 4, 0}})); } From c1ac2582d738a17ebdd56e57c5f181543c414170 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 13:54:46 -0700 Subject: [PATCH 06/25] [FixedString] Implement primaryassign() overloads --- include/fixed_containers/fixed_string.hpp | 36 ++++++ test/fixed_string_test.cpp | 140 ++++++++++++++++++++++ 2 files changed, 176 insertions(+) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index 9da4f33a..13162bf6 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -135,6 +135,42 @@ class FixedString null_terminate(loc); } + constexpr FixedString& assign( + size_type count, + CharT ch, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + vec().assign(count, ch, loc); + null_terminate(loc); + return *this; + } + template + constexpr FixedString& assign( + InputIt first, + InputIt last, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + vec().assign(first, last, loc); + null_terminate(loc); + return *this; + } + constexpr FixedString& assign( + std::initializer_list ilist, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + vec().assign(ilist, loc); + null_terminate(loc); + return *this; + } + constexpr FixedString& assign( + const std::string_view& t, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + vec().assign(t.begin(), t.end(), loc); + null_terminate(loc); + return *this; + } + [[nodiscard]] constexpr reference operator[](size_type i) noexcept { // Cannot capture real source_location for operator[] diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index b34c17be..281e985b 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -80,6 +80,146 @@ TEST(FixedString, StringViewConstructor) static_assert(v1.max_size() == 17); } +TEST(FixedString, AssignValue) +{ + { + constexpr auto v1 = []() + { + FixedString<7> v{"012"}; + v.assign(5, '3'); + return v; + }(); + + static_assert(v1 == "33333"); + static_assert(v1.size() == 5); + } + + { + constexpr auto v2 = []() + { + FixedString<7> v{"012"}; + v.assign(5, '5'); + v.assign(2, '9'); + return v; + }(); + + static_assert(v2 == "99"); + static_assert(v2.size() == 2); + static_assert(v2.max_size() == 7); + } + + { + auto v3 = []() + { + FixedString<7> v{"012"}; + v.assign(5, '5'); + v.assign(2, '9'); + return v; + }(); + + EXPECT_EQ(2, v3.size()); + EXPECT_EQ(v3, "99"); + } +} + +TEST(FixedString, AssignValue_ExceedsCapacity) +{ + FixedString<3> v1{"012"}; + EXPECT_DEATH(v1.assign(5, '9'), ""); +} + +TEST(FixedString, AssignRange) +{ + { + constexpr auto v1 = []() + { + std::array a{'9', '9'}; + FixedString<7> v{"012"}; + v.assign(a.begin(), a.end()); + return v; + }(); + + static_assert(v1 == "99"); + static_assert(v1.size() == 2); + static_assert(v1.max_size() == 7); + } + { + auto v2 = []() + { + std::array a{'9', '9'}; + FixedString<7> v{"012"}; + v.assign(a.begin(), a.end()); + return v; + }(); + + EXPECT_EQ(v2, "99"); + EXPECT_EQ(2, v2.size()); + } +} + +TEST(FixedString, AssignRange_ExceedsCapacity) +{ + FixedString<3> v1{"012"}; + std::array a{'9', '9'}; + EXPECT_DEATH(v1.assign(a.begin(), a.end()), ""); +} + +TEST(FixedString, AssignInitializerList) +{ + { + constexpr auto v1 = []() + { + FixedString<7> v{"012"}; + v.assign({'9', '9'}); + return v; + }(); + + static_assert(v1 == "99"); + static_assert(v1.size() == 2); + static_assert(v1.max_size() == 7); + } + { + auto v2 = []() + { + FixedString<7> v{"012"}; + v.assign({'9', '9'}); + return v; + }(); + + EXPECT_EQ(v2, "99"); + EXPECT_EQ(2, v2.size()); + } +} + +TEST(FixedString, AssignStringView) +{ + { + constexpr auto v1 = []() + { + FixedString<7> v{"012"}; + std::string_view s{"99"}; + v.assign(s); + return v; + }(); + + static_assert(v1 == "99"); + static_assert(v1.size() == 2); + static_assert(v1.max_size() == 7); + } + { + auto v2 = []() + { + FixedString<7> v{"012"}; + std::string_view s{"99"}; + v.assign(s); + return v; + }(); + + EXPECT_EQ(v2, "99"); + EXPECT_EQ(2, v2.size()); + } +} + TEST(FixedString, BracketOperator) { constexpr auto v1 = []() From 944c60eb46dd3184411ee81a33dcb160515cd9f9 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 13:55:07 -0700 Subject: [PATCH 07/25] [Vector/Deque<->String consistency] Reorder Assign_ExceedsCapacity tests --- test/fixed_deque_test.cpp | 24 ++++++++++++------------ test/fixed_vector_test.cpp | 12 ++++++------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/test/fixed_deque_test.cpp b/test/fixed_deque_test.cpp index 2b2f6bd3..ee1bea2d 100644 --- a/test/fixed_deque_test.cpp +++ b/test/fixed_deque_test.cpp @@ -1228,6 +1228,18 @@ TEST(FixedDeque, AssignValue) run_test(FixedDequeInitialStateLastIndex{}); } +TEST(FixedDeque, AssignValue_ExceedsCapacity) +{ + auto run_test = [](Factory&&) + { + auto v1 = Factory::template create({0, 1, 2}); + EXPECT_DEATH(v1.assign(5, 100), ""); + }; + + run_test(FixedDequeInitialStateFirstIndex{}); + run_test(FixedDequeInitialStateLastIndex{}); +} + TEST(FixedDeque, AssignRange) { auto run_test = [](Factory&&) @@ -1263,18 +1275,6 @@ TEST(FixedDeque, AssignRange) run_test(FixedDequeInitialStateLastIndex{}); } -TEST(FixedDeque, AssignValue_ExceedsCapacity) -{ - auto run_test = [](Factory&&) - { - auto v1 = Factory::template create({0, 1, 2}); - EXPECT_DEATH(v1.assign(5, 100), ""); - }; - - run_test(FixedDequeInitialStateFirstIndex{}); - run_test(FixedDequeInitialStateLastIndex{}); -} - TEST(FixedDeque, AssignRange_ExceedsCapacity) { auto run_test = [](Factory&&) diff --git a/test/fixed_vector_test.cpp b/test/fixed_vector_test.cpp index 41911542..9c746e34 100644 --- a/test/fixed_vector_test.cpp +++ b/test/fixed_vector_test.cpp @@ -1349,6 +1349,12 @@ TEST(FixedVector, AssignValue) } } +TEST(FixedVector, AssignValue_ExceedsCapacity) +{ + FixedVector v1{0, 1, 2}; + EXPECT_DEATH(v1.assign(5, 100), ""); +} + TEST(FixedVector, AssignRange) { { @@ -1378,12 +1384,6 @@ TEST(FixedVector, AssignRange) } } -TEST(FixedVector, AssignValue_ExceedsCapacity) -{ - FixedVector v1{0, 1, 2}; - EXPECT_DEATH(v1.assign(5, 100), ""); -} - TEST(FixedVector, AssignRange_ExceedsCapacity) { FixedVector v1{0, 1, 2}; From e84bc66c4ab938e57862537d6f1f53add130a131 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 13:58:23 -0700 Subject: [PATCH 08/25] [FixedString] Implement front()/back() --- include/fixed_containers/fixed_string.hpp | 21 ++++++++ test/fixed_string_test.cpp | 64 +++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index 13162bf6..117bf96d 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -198,6 +198,27 @@ class FixedString return vec().at(i, loc); } + constexpr reference front( + const std_transition::source_location& loc = std_transition::source_location::current()) + { + return vec().front(loc); + } + constexpr const_reference front(const std_transition::source_location& loc = + std_transition::source_location::current()) const + { + return vec().front(loc); + } + constexpr reference back( + const std_transition::source_location& loc = std_transition::source_location::current()) + { + return vec().back(loc); + } + constexpr const_reference back(const std_transition::source_location& loc = + std_transition::source_location::current()) const + { + return vec().back(loc); + } + [[nodiscard]] constexpr const char* data() const noexcept { return vec().data(); } [[nodiscard]] constexpr char* data() noexcept { return vec().data(); } diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 281e985b..e9863856 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -293,6 +293,70 @@ TEST(FixedString, At_OutOfBounds) EXPECT_DEATH(static_cast(v3.at(v2.size())), ""); } +TEST(FixedString, Front) +{ + constexpr auto v1 = []() + { + FixedString<8> v{"z12"}; + return v; + }(); + + static_assert(v1.front() == 'z'); + static_assert(v1 == "z12"); + static_assert(v1.size() == 3); + + FixedString<8> v2{"abc"}; + const auto& v2_const_ref = v2; + + EXPECT_EQ(v2.front(), 'a'); // non-const variant + v2.front() = 'a'; + EXPECT_EQ(v2_const_ref.front(), 'a'); // const variant +} + +TEST(FixedString, Front_EmptyContainer) +{ + { + const FixedString<3> v{}; + EXPECT_DEATH(v.front(), ""); + } + { + FixedString<3> v{}; + EXPECT_DEATH(v.front(), ""); + } +} + +TEST(FixedString, Back) +{ + constexpr auto v1 = []() + { + FixedString<8> v{"01w"}; + return v; + }(); + + static_assert(v1.back() == 'w'); + static_assert(v1 == "01w"); + static_assert(v1.size() == 3); + + FixedString<8> v2{"abc"}; + const auto& v2_const_ref = v2; + + EXPECT_EQ(v2.back(), 'c'); // non-const variant + v2.back() = 'c'; + EXPECT_EQ(v2_const_ref.back(), 'c'); // const variant +} + +TEST(FixedString, Back_EmptyContainer) +{ + { + const FixedString<3> v{}; + EXPECT_DEATH(v.back(), ""); + } + { + FixedString<3> v{}; + EXPECT_DEATH(v.back(), ""); + } +} + TEST(FixedString, IteratorAssignment) { FixedString<8>::iterator it; // Default construction From 6c8fd8f32a2859401e102a6f3fd8b48a13861038 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 14:02:18 -0700 Subject: [PATCH 09/25] [FixedString] Reorder data() test --- test/fixed_string_test.cpp | 74 +++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index e9863856..79a924c0 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -357,6 +357,43 @@ TEST(FixedString, Back_EmptyContainer) } } +TEST(FixedString, Data) +{ + { + constexpr auto v1 = []() + { + FixedString<8> v{"012"}; + return v; + }(); + + static_assert(*std::next(v1.data(), 0) == '0'); + static_assert(*std::next(v1.data(), 1) == '1'); + static_assert(*std::next(v1.data(), 2) == '2'); + static_assert(*std::next(v1.data(), 3) == '\0'); + static_assert(*std::next(v1.data(), 8) == '\0'); + + EXPECT_EQ(*std::next(v1.data(), 0), '0'); + EXPECT_EQ(*std::next(v1.data(), 1), '1'); + EXPECT_EQ(*std::next(v1.data(), 2), '2'); + EXPECT_EQ(*std::next(v1.data(), 3), '\0'); + EXPECT_EQ(*std::next(v1.data(), 8), '\0'); + + static_assert(v1.size() == 3); + } + + { + FixedString<8> v2{"abc"}; + const auto& v2_const_ref = v2; + + auto it = std::next(v2.data(), 1); + EXPECT_EQ(*it, 'b'); // non-const variant + *it = 'z'; + EXPECT_EQ(*it, 'z'); + + EXPECT_EQ(*std::next(v2_const_ref.data(), 1), 'z'); // const variant + } +} + TEST(FixedString, IteratorAssignment) { FixedString<8>::iterator it; // Default construction @@ -732,43 +769,6 @@ TEST(FixedString, StringViewConversion) static_assert(as_view == std::string_view{"12345"}); } -TEST(FixedString, Data) -{ - { - constexpr auto v1 = []() - { - FixedString<8> v{"012"}; - return v; - }(); - - static_assert(*std::next(v1.data(), 0) == '0'); - static_assert(*std::next(v1.data(), 1) == '1'); - static_assert(*std::next(v1.data(), 2) == '2'); - static_assert(*std::next(v1.data(), 3) == '\0'); - static_assert(*std::next(v1.data(), 8) == '\0'); - - EXPECT_EQ(*std::next(v1.data(), 0), '0'); - EXPECT_EQ(*std::next(v1.data(), 1), '1'); - EXPECT_EQ(*std::next(v1.data(), 2), '2'); - EXPECT_EQ(*std::next(v1.data(), 3), '\0'); - EXPECT_EQ(*std::next(v1.data(), 8), '\0'); - - static_assert(v1.size() == 3); - } - - { - FixedString<8> v2{"abc"}; - const auto& v2_const_ref = v2; - - auto it = std::next(v2.data(), 1); - EXPECT_EQ(*it, 'b'); // non-const variant - *it = 'z'; - EXPECT_EQ(*it, 'z'); - - EXPECT_EQ(*std::next(v2_const_ref.data(), 1), 'z'); // const variant - } -} - TEST(FixedString, Equality) { constexpr auto v1 = FixedString<12>{"012"}; From 4806fcdf1af73ab73f00a5e1e871a322f41dedd4 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 14:03:48 -0700 Subject: [PATCH 10/25] [FixedString] Implement c_str() --- include/fixed_containers/fixed_string.hpp | 1 + test/fixed_string_test.cpp | 25 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index 117bf96d..2f6972c1 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -221,6 +221,7 @@ class FixedString [[nodiscard]] constexpr const char* data() const noexcept { return vec().data(); } [[nodiscard]] constexpr char* data() noexcept { return vec().data(); } + [[nodiscard]] constexpr const CharT* c_str() const noexcept { return data(); } constexpr iterator begin() noexcept { return vec().begin(); } constexpr const_iterator begin() const noexcept { return cbegin(); } diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 79a924c0..87f6ede4 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -394,6 +394,31 @@ TEST(FixedString, Data) } } +TEST(FixedString, CStr) +{ + { + constexpr auto v1 = []() + { + FixedString<8> v{"012"}; + return v; + }(); + + static_assert(*std::next(v1.c_str(), 0) == '0'); + static_assert(*std::next(v1.c_str(), 1) == '1'); + static_assert(*std::next(v1.c_str(), 2) == '2'); + static_assert(*std::next(v1.c_str(), 3) == '\0'); + static_assert(*std::next(v1.c_str(), 8) == '\0'); + + EXPECT_EQ(*std::next(v1.c_str(), 0), '0'); + EXPECT_EQ(*std::next(v1.c_str(), 1), '1'); + EXPECT_EQ(*std::next(v1.c_str(), 2), '2'); + EXPECT_EQ(*std::next(v1.c_str(), 3), '\0'); + EXPECT_EQ(*std::next(v1.c_str(), 8), '\0'); + + static_assert(v1.size() == 3); + } +} + TEST(FixedString, IteratorAssignment) { FixedString<8>::iterator it; // Default construction From a41ffe02a15339ad037a832812745fe068e44738 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 14:04:57 -0700 Subject: [PATCH 11/25] [FixedString] Reorder string_view auto-conversion --- include/fixed_containers/fixed_string.hpp | 10 +++++----- test/fixed_string_test.cpp | 24 +++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index 2f6972c1..e91dd588 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -223,6 +223,11 @@ class FixedString [[nodiscard]] constexpr char* data() noexcept { return vec().data(); } [[nodiscard]] constexpr const CharT* c_str() const noexcept { return data(); } + explicit(false) constexpr operator std::string_view() const + { + return std::string_view(data(), length()); + } + constexpr iterator begin() noexcept { return vec().begin(); } constexpr const_iterator begin() const noexcept { return cbegin(); } constexpr const_iterator cbegin() const noexcept { return vec().cbegin(); } @@ -267,11 +272,6 @@ class FixedString null_terminate(loc); } - explicit(false) constexpr operator std::string_view() const - { - return std::string_view(data(), length()); - } - template constexpr bool operator==(const FixedString& other) const diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 87f6ede4..8f88e7cc 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -419,6 +419,18 @@ TEST(FixedString, CStr) } } +TEST(FixedString, StringViewConversion) +{ + auto function_that_takes_string_view = [](const std::string_view&) {}; + + static constexpr FixedString<7> v1{"12345"}; + function_that_takes_string_view(v1); + constexpr std::string_view as_view = v1; + + static_assert(consteval_compare::equal<5, as_view.size()>); + static_assert(as_view == std::string_view{"12345"}); +} + TEST(FixedString, IteratorAssignment) { FixedString<8>::iterator it; // Default construction @@ -782,18 +794,6 @@ TEST(FixedString, PushBack_ExceedsCapacity) EXPECT_DEATH(v.push_back('2'), ""); } -TEST(FixedString, StringViewConversion) -{ - auto function_that_takes_string_view = [](const std::string_view&) {}; - - static constexpr FixedString<7> v1{"12345"}; - function_that_takes_string_view(v1); - constexpr std::string_view as_view = v1; - - static_assert(consteval_compare::equal<5, as_view.size()>); - static_assert(as_view == std::string_view{"12345"}); -} - TEST(FixedString, Equality) { constexpr auto v1 = FixedString<12>{"012"}; From 0893f233209db51a084c9059ca7bc1976c5930a8 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 14:08:34 -0700 Subject: [PATCH 12/25] [FixedString] Implement reserve() --- include/fixed_containers/fixed_string.hpp | 10 ++++++++++ test/fixed_string_test.cpp | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index e91dd588..4112c4df 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -246,6 +246,16 @@ class FixedString [[nodiscard]] constexpr std::size_t length() const noexcept { return vec().size(); } [[nodiscard]] constexpr std::size_t size() const noexcept { return length(); } [[nodiscard]] constexpr std::size_t max_size() const noexcept { return MAXIMUM_LENGTH; } + constexpr void reserve(const std::size_t new_capacity, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + { + if (preconditions::test(new_capacity <= MAXIMUM_LENGTH)) + { + Checking::length_error(new_capacity, loc); + } + // Do nothing + } [[nodiscard]] constexpr std::size_t capacity() const noexcept { return max_size(); } constexpr iterator erase( diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 8f88e7cc..0acef3da 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -689,6 +689,23 @@ TEST(FixedString, Empty) static_assert(v1.max_size() == 7); } +TEST(FixedString, Reserve) +{ + constexpr auto v1 = []() + { + FixedString<11> v{}; + v.reserve(5); + return v; + }(); + + static_assert(v1.capacity() == 11); + static_assert(v1.max_size() == 11); + + FixedString<7> v2{}; + v2.reserve(5); + EXPECT_DEATH(v2.reserve(15), ""); +} + TEST(FixedString, EraseRange) { constexpr auto v1 = []() From 835a383a40c87e982117a54cf58898372979dc19 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 14:09:10 -0700 Subject: [PATCH 13/25] [FixedString] Reorder empty()/size()/max_size() tests --- test/fixed_string_test.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 0acef3da..c61bf432 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -649,19 +649,12 @@ TEST(FixedString, IterationBasic) const_span_of_const_ref(v); } -TEST(FixedString, CapacityAndMaxSize) +TEST(FixedString, Empty) { - { - constexpr FixedString<3> v1{}; - static_assert(v1.capacity() == 3); - static_assert(v1.max_size() == 3); - } + constexpr auto v1 = []() { return FixedString<7>{}; }(); - { - FixedString<3> v1{}; - EXPECT_EQ(3, v1.capacity()); - EXPECT_EQ(3, v1.max_size()); - } + static_assert(v1.empty()); + static_assert(v1.max_size() == 7); } TEST(FixedString, LengthAndSize) @@ -681,12 +674,19 @@ TEST(FixedString, LengthAndSize) } } -TEST(FixedString, Empty) +TEST(FixedString, CapacityAndMaxSize) { - constexpr auto v1 = []() { return FixedString<7>{}; }(); + { + constexpr FixedString<3> v1{}; + static_assert(v1.capacity() == 3); + static_assert(v1.max_size() == 3); + } - static_assert(v1.empty()); - static_assert(v1.max_size() == 7); + { + FixedString<3> v1{}; + EXPECT_EQ(3, v1.capacity()); + EXPECT_EQ(3, v1.max_size()); + } } TEST(FixedString, Reserve) From 9911870c1633bc9eec7754dfedc947a69f0e4519 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 14:10:52 -0700 Subject: [PATCH 14/25] [FixedString] Implement clear() --- include/fixed_containers/fixed_string.hpp | 6 ++++++ test/fixed_string_test.cpp | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index 4112c4df..2b9e70e6 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -258,6 +258,12 @@ class FixedString } [[nodiscard]] constexpr std::size_t capacity() const noexcept { return max_size(); } + constexpr void clear() noexcept + { + vec().clear(); + null_terminate(std_transition::source_location::current()); + } + constexpr iterator erase( const_iterator position, const std_transition::source_location& loc = std_transition::source_location::current()) diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index c61bf432..1affb35e 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -706,6 +706,21 @@ TEST(FixedString, Reserve) EXPECT_DEATH(v2.reserve(15), ""); } +TEST(FixedString, Clear) +{ + constexpr auto v1 = []() + { + FixedString<7> v{"012"}; + v.assign(5, 'a'); + v.clear(); + return v; + }(); + + static_assert(v1.empty()); + static_assert(v1.capacity() == 7); + static_assert(v1.max_size() == 7); +} + TEST(FixedString, EraseRange) { constexpr auto v1 = []() From 17c225889f7e72a45527bb9797c97e05ec4f9fb6 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 14:17:50 -0700 Subject: [PATCH 15/25] [Vector/Deque<->String consistency] Reorder and rename insert(iterators) test --- test/fixed_deque_test.cpp | 26 +++++++++++++------------- test/fixed_vector_test.cpp | 14 +++++++------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/test/fixed_deque_test.cpp b/test/fixed_deque_test.cpp index ee1bea2d..637dc70b 100644 --- a/test/fixed_deque_test.cpp +++ b/test/fixed_deque_test.cpp @@ -1441,6 +1441,19 @@ TEST(FixedDeque, InsertIterator) run_test(FixedDequeInitialStateLastIndex{}); } +TEST(FixedDeque, InsertIterator_ExceedsCapacity) +{ + auto run_test = [](Factory&&) + { + auto v1 = Factory::template create({0, 1, 2}); + std::array a{3, 4}; + EXPECT_DEATH(v1.insert(v1.begin() + 1, a.begin(), a.end()), ""); + }; + + run_test(FixedDequeInitialStateFirstIndex{}); + run_test(FixedDequeInitialStateLastIndex{}); +} + TEST(FixedDeque, InsertInputIterator) { auto run_test = [](Factory&&) @@ -1470,19 +1483,6 @@ TEST(FixedDeque, InsertInputIterator_ExceedsCapacity) run_test(FixedDequeInitialStateLastIndex{}); } -TEST(FixedDeque, InsertRange_ExceedsCapacity) -{ - auto run_test = [](Factory&&) - { - auto v1 = Factory::template create({0, 1, 2}); - std::array a{3, 4}; - EXPECT_DEATH(v1.insert(v1.begin() + 1, a.begin(), a.end()), ""); - }; - - run_test(FixedDequeInitialStateFirstIndex{}); - run_test(FixedDequeInitialStateLastIndex{}); -} - TEST(FixedDeque, InsertInitializerList) { auto run_test = [](Factory&&) diff --git a/test/fixed_vector_test.cpp b/test/fixed_vector_test.cpp index 9c746e34..7d20b9f9 100644 --- a/test/fixed_vector_test.cpp +++ b/test/fixed_vector_test.cpp @@ -1518,6 +1518,13 @@ TEST(FixedVector, InsertIterator) } } +TEST(FixedVector, InsertIterator_ExceedsCapacity) +{ + FixedVector v1{0, 1, 2}; + std::array a{3, 4}; + EXPECT_DEATH(v1.insert(v1.begin() + 1, a.begin(), a.end()), ""); +} + TEST(FixedVector, InsertInputIterator) { MockIntStream stream{3}; @@ -1535,13 +1542,6 @@ TEST(FixedVector, InsertInputIterator_ExceedsCapacity) EXPECT_DEATH(v.insert(v.begin() + 2, stream.begin(), stream.end()), ""); } -TEST(FixedVector, InsertRange_ExceedsCapacity) -{ - FixedVector v1{0, 1, 2}; - std::array a{3, 4}; - EXPECT_DEATH(v1.insert(v1.begin() + 1, a.begin(), a.end()), ""); -} - TEST(FixedVector, InsertInitializerList) { { From cb28b96009f59a6f0d0d2be3cedef214991ccf6a Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 14:31:57 -0700 Subject: [PATCH 16/25] MockIntStream -> MockIntegralStream --- test/fixed_deque_test.cpp | 6 +++--- test/fixed_vector_test.cpp | 6 +++--- test/mock_testing_types.hpp | 18 ++++++++++-------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/test/fixed_deque_test.cpp b/test/fixed_deque_test.cpp index 637dc70b..b9543f46 100644 --- a/test/fixed_deque_test.cpp +++ b/test/fixed_deque_test.cpp @@ -145,7 +145,7 @@ TEST(FixedDeque, IteratorConstructor) TEST(FixedDeque, InputIteratorConstructor) { - MockIntStream stream{3}; + MockIntegraStream stream{3}; FixedDeque v{stream.begin(), stream.end()}; ASSERT_EQ(3, v.size()); EXPECT_TRUE(std::ranges::equal(v, std::array{3, 2, 1})); @@ -1458,7 +1458,7 @@ TEST(FixedDeque, InsertInputIterator) { auto run_test = [](Factory&&) { - MockIntStream stream{3}; + MockIntegraStream stream{3}; auto v = Factory::template create({10, 20, 30, 40}); auto it = v.insert(v.begin() + 2, stream.begin(), stream.end()); ASSERT_EQ(7, v.size()); @@ -1474,7 +1474,7 @@ TEST(FixedDeque, InsertInputIterator_ExceedsCapacity) { auto run_test = [](Factory&&) { - MockIntStream stream{3}; + MockIntegraStream stream{3}; auto v = Factory::template create({10, 20, 30, 40}); EXPECT_DEATH(v.insert(v.begin() + 2, stream.begin(), stream.end()), ""); }; diff --git a/test/fixed_vector_test.cpp b/test/fixed_vector_test.cpp index 7d20b9f9..428e4d0a 100644 --- a/test/fixed_vector_test.cpp +++ b/test/fixed_vector_test.cpp @@ -448,7 +448,7 @@ TEST(FixedVector, IteratorConstructor) TEST(FixedVector, InputIteratorConstructor) { - MockIntStream stream{3}; + MockIntegraStream stream{3}; FixedVector v{stream.begin(), stream.end()}; ASSERT_EQ(3, v.size()); EXPECT_TRUE(std::ranges::equal(v, std::array{3, 2, 1})); @@ -1527,7 +1527,7 @@ TEST(FixedVector, InsertIterator_ExceedsCapacity) TEST(FixedVector, InsertInputIterator) { - MockIntStream stream{3}; + MockIntegraStream stream{3}; FixedVector v{10, 20, 30, 40}; auto it = v.insert(v.begin() + 2, stream.begin(), stream.end()); ASSERT_EQ(7, v.size()); @@ -1537,7 +1537,7 @@ TEST(FixedVector, InsertInputIterator) TEST(FixedVector, InsertInputIterator_ExceedsCapacity) { - MockIntStream stream{3}; + MockIntegraStream stream{3}; FixedVector v{10, 20, 30, 40}; EXPECT_DEATH(v.insert(v.begin() + 2, stream.begin(), stream.end()), ""); } diff --git a/test/mock_testing_types.hpp b/test/mock_testing_types.hpp index e95cc289..2b652958 100644 --- a/test/mock_testing_types.hpp +++ b/test/mock_testing_types.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -225,20 +226,21 @@ struct MockAComparableToB } }; -class MockIntStream +template +class MockIntegraStream { class MockInputIterator { public: - using reference = const int&; - using value_type = const int; - using pointer = const int*; + using reference = const T&; + using value_type = const T; + using pointer = const T*; using iterator = MockInputIterator; using iterator_category = std::input_iterator_tag; using difference_type = std::ptrdiff_t; private: - std::optional remaining_; + std::optional remaining_; public: constexpr MockInputIterator() noexcept @@ -246,7 +248,7 @@ class MockIntStream { } - explicit constexpr MockInputIterator(int& remaining) noexcept + explicit constexpr MockInputIterator(T& remaining) noexcept : remaining_(&remaining) { } @@ -279,10 +281,10 @@ class MockIntStream }; private: - int remaining_; + T remaining_; public: - explicit constexpr MockIntStream(const int stream_size) + explicit constexpr MockIntegraStream(const T stream_size) : remaining_{stream_size} { } From 4bffaae01cdbd38ce4fc1c59a79b6bb546543622 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 14:36:29 -0700 Subject: [PATCH 17/25] [FixedString] Implement primary insert() overloads --- BUILD.bazel | 1 + include/fixed_containers/fixed_string.hpp | 35 +++++ test/fixed_string_test.cpp | 157 ++++++++++++++++++++++ 3 files changed, 193 insertions(+) diff --git a/BUILD.bazel b/BUILD.bazel index 7cdb032b..9995fcbf 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -693,6 +693,7 @@ cc_test( ":concepts", ":consteval_compare", ":fixed_string", + ":mock_testing_types", ":test_utilities_common", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index 2b9e70e6..ebbc4243 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -264,6 +264,41 @@ class FixedString null_terminate(std_transition::source_location::current()); } + constexpr iterator insert( + const_iterator it, + CharT v, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + ScopedNullTermination guard{this, loc}; + return vec().insert(it, v, loc); + } + template + constexpr iterator insert( + const_iterator it, + InputIt first, + InputIt last, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + ScopedNullTermination guard{this, loc}; + return vec().insert(it, first, last, loc); + } + constexpr iterator insert( + const_iterator it, + std::initializer_list ilist, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + ScopedNullTermination guard{this, loc}; + return vec().insert(it, ilist, loc); + } + constexpr iterator insert( + const_iterator it, + std::string_view s, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + ScopedNullTermination guard{this, loc}; + return vec().insert(it, s.begin(), s.end(), loc); + } + constexpr iterator erase( const_iterator position, const std_transition::source_location& loc = std_transition::source_location::current()) diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 1affb35e..0ea767eb 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -1,5 +1,6 @@ #include "fixed_containers/fixed_string.hpp" +#include "mock_testing_types.hpp" #include "test_utilities_common.hpp" #include "fixed_containers/concepts.hpp" @@ -7,6 +8,7 @@ #include +#include #include #include @@ -721,6 +723,161 @@ TEST(FixedString, Clear) static_assert(v1.max_size() == 7); } +TEST(FixedString, InsertValue) +{ + { + constexpr auto v1 = []() + { + FixedString<7> v{"0123"}; + v.insert(v.begin(), 'a'); + const char value = 'e'; + v.insert(v.begin() + 2, value); + return v; + }(); + + static_assert(v1 == "a0e123"); + static_assert(v1.size() == 6); + static_assert(v1.max_size() == 7); + } + { + // For off-by-one issues, make the capacity just fit + constexpr auto v2 = []() + { + FixedString<5> v{"012"}; + v.insert(v.begin(), 'a'); + const char value = 'e'; + v.insert(v.begin() + 2, value); + return v; + }(); + + static_assert(v2 == "a0e12"); + static_assert(v2.size() == 5); + static_assert(v2.max_size() == 5); + } +} + +TEST(FixedString, InsertValue_ExceedsCapacity) +{ + FixedString<4> v1{"0123"}; + EXPECT_DEATH(v1.insert(v1.begin() + 1, '5'), ""); +} + +TEST(FixedString, InsertIterator) +{ + { + constexpr auto v1 = []() + { + std::array a{'a', 'e'}; + FixedString<7> v{"0123"}; + v.insert(v.begin() + 2, a.begin(), a.end()); + return v; + }(); + + static_assert(v1 == "01ae23"); + static_assert(v1.size() == 6); + static_assert(v1.max_size() == 7); + } + { + // For off-by-one issues, make the capacity just fit + constexpr auto v2 = []() + { + std::array a{'a', 'e'}; + FixedString<5> v{"012"}; + v.insert(v.begin() + 2, a.begin(), a.end()); + return v; + }(); + + static_assert(v2 == "01ae2"); + static_assert(v2.size() == 5); + static_assert(v2.max_size() == 5); + } + + { + std::array a{'a', 'e'}; + FixedString<7> v{"0123"}; + auto it = v.insert(v.begin() + 2, a.begin(), a.end()); + EXPECT_EQ(v, "01ae23"); + EXPECT_EQ(it, v.begin() + 2); + } +} + +TEST(FixedString, InsertIterator_ExceedsCapacity) +{ + FixedString<4> v1{"012"}; + std::array a{'3', '4'}; + EXPECT_DEATH(v1.insert(v1.begin() + 1, a.begin(), a.end()), ""); +} + +TEST(FixedString, InsertInputIterator) +{ + MockIntegraStream stream{static_cast(3)}; + FixedString<14> v{"abcd"}; + auto it = v.insert(v.begin() + 2, stream.begin(), stream.end()); + ASSERT_EQ(7, v.size()); + EXPECT_TRUE(std::ranges::equal( + v, + std::array{ + 'a', 'b', static_cast(3), static_cast(2), static_cast(1), 'c', 'd'})); + EXPECT_EQ(it, v.begin() + 2); +} + +TEST(FixedString, InsertInputIterator_ExceedsCapacity) +{ + MockIntegraStream stream{3}; + FixedString<6> v{"abcd"}; + EXPECT_DEATH(v.insert(v.begin() + 2, stream.begin(), stream.end()), ""); +} + +TEST(FixedString, InsertInitializerList) +{ + { + // For off-by-one issues, make the capacity just fit + constexpr auto v1 = []() + { + FixedString<5> v{"012"}; + v.insert(v.begin() + 2, {'a', 'e'}); + return v; + }(); + + static_assert(v1 == "01ae2"); + static_assert(v1.size() == 5); + static_assert(v1.max_size() == 5); + } + + { + FixedString<7> v{"0123"}; + auto it = v.insert(v.begin() + 2, {'a', 'e'}); + EXPECT_EQ(v, "01ae23"); + EXPECT_EQ(it, v.begin() + 2); + } +} + +TEST(FixedString, InsertStringView) +{ + { + // For off-by-one issues, make the capacity just fit + constexpr auto v1 = []() + { + FixedString<5> v{"012"}; + std::string_view s = "ae"; + v.insert(v.begin() + 2, s); + return v; + }(); + + static_assert(v1 == "01ae2"); + static_assert(v1.size() == 5); + static_assert(v1.max_size() == 5); + } + + { + FixedString<7> v{"0123"}; + std::string_view s = "ae"; + auto it = v.insert(v.begin() + 2, s); + EXPECT_EQ(v, "01ae23"); + EXPECT_EQ(it, v.begin() + 2); + } +} + TEST(FixedString, EraseRange) { constexpr auto v1 = []() From cd9dcee0cd40f2147b4badadb838db53cdd7c8cb Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 14:39:31 -0700 Subject: [PATCH 18/25] [FixedString] Implement pop_back() --- include/fixed_containers/fixed_string.hpp | 7 +++++++ test/fixed_string_test.cpp | 25 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index ebbc4243..0194098e 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -323,6 +323,13 @@ class FixedString null_terminate(loc); } + constexpr void pop_back( + const std_transition::source_location& loc = std_transition::source_location::current()) + { + vec().pop_back(loc); + null_terminate(loc); + } + template constexpr bool operator==(const FixedString& other) const diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 0ea767eb..000a08e3 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -983,6 +983,31 @@ TEST(FixedString, PushBack_ExceedsCapacity) EXPECT_DEATH(v.push_back('2'), ""); } +TEST(FixedString, PopBack) +{ + constexpr auto v1 = []() + { + FixedString<11> v{"012"}; + v.pop_back(); + return v; + }(); + + static_assert(v1[0] == '0'); + static_assert(v1[1] == '1'); + static_assert(v1.size() == 2); + static_assert(v1.max_size() == 11); + + FixedString<17> v2{"abc"}; + v2.pop_back(); + EXPECT_EQ(v2, "ab"); +} + +TEST(FixedString, PopBack_Empty) +{ + FixedString<5> v1{}; + EXPECT_DEATH(v1.pop_back(), ""); +} + TEST(FixedString, Equality) { constexpr auto v1 = FixedString<12>{"012"}; From 13259d11c8bb1b9a6288e1cae935f33c5fd9f18b Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 14:51:48 -0700 Subject: [PATCH 19/25] [FixedString] Implement primary append() and += overloads --- include/fixed_containers/fixed_string.hpp | 44 +++++++ test/fixed_string_test.cpp | 139 ++++++++++++++++++++++ 2 files changed, 183 insertions(+) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index 0194098e..f8a63dc7 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -330,6 +330,50 @@ class FixedString null_terminate(loc); } + template + constexpr FixedString& append( + InputIt first, + InputIt last, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + vec().insert(vec().cend(), first, last, loc); + null_terminate(loc); + return *this; + } + constexpr FixedString& append( + std::initializer_list ilist, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + vec().insert(vec().cend(), ilist, loc); + null_terminate(loc); + return *this; + } + constexpr FixedString& append( + const std::string_view& t, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + vec().insert(vec().cend(), t.begin(), t.end(), loc); + null_terminate(loc); + return *this; + } + + constexpr FixedString& operator+=(CharT ch) + { + return append(ch, std_transition::source_location::current()); + } + constexpr FixedString& operator+=(const CharT* s) + { + return append(std::string_view{s}, std_transition::source_location::current()); + } + constexpr FixedString& operator+=(std::initializer_list ilist) + { + return append(ilist, std_transition::source_location::current()); + } + constexpr FixedString& operator+=(const std::string_view& t) + { + return append(t, std_transition::source_location::current()); + } + template constexpr bool operator==(const FixedString& other) const diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 000a08e3..0b8cf9a0 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -1008,6 +1008,145 @@ TEST(FixedString, PopBack_Empty) EXPECT_DEATH(v1.pop_back(), ""); } +TEST(FixedString, AppendIterator) +{ + { + constexpr auto v1 = []() + { + std::array a{'a', 'e'}; + FixedString<7> v{"0123"}; + v.append(a.begin(), a.end()); + return v; + }(); + + static_assert(v1 == "0123ae"); + static_assert(v1.size() == 6); + static_assert(v1.max_size() == 7); + } + { + // For off-by-one issues, make the capacity just fit + constexpr auto v2 = []() + { + std::array a{'a', 'e'}; + FixedString<5> v{"012"}; + v.append(a.begin(), a.end()); + return v; + }(); + + static_assert(v2 == "012ae"); + static_assert(v2.size() == 5); + static_assert(v2.max_size() == 5); + } + + { + std::array a{'a', 'e'}; + FixedString<7> v{"0123"}; + auto& self = v.append(a.begin(), a.end()); + EXPECT_EQ(v, "0123ae"); + EXPECT_EQ(self, v); + } +} + +TEST(FixedString, AppendIterator_ExceedsCapacity) +{ + FixedString<4> v1{"012"}; + std::array a{'3', '4'}; + EXPECT_DEATH(v1.append(a.begin(), a.end()), ""); +} + +TEST(FixedString, AppendInputIterator) +{ + MockIntegraStream stream{static_cast(3)}; + FixedString<14> v{"abcd"}; + auto& self = v.append(stream.begin(), stream.end()); + ASSERT_EQ(7, v.size()); + EXPECT_TRUE(std::ranges::equal(v, + std::array{ + 'a', + 'b', + 'c', + 'd', + static_cast(3), + static_cast(2), + static_cast(1), + })); + EXPECT_EQ(self, v); +} + +TEST(FixedString, AppendInputIterator_ExceedsCapacity) +{ + MockIntegraStream stream{3}; + FixedString<6> v{"abcd"}; + EXPECT_DEATH(v.append(stream.begin(), stream.end()), ""); +} + +TEST(FixedString, AppendInitializerList) +{ + { + // For off-by-one issues, make the capacity just fit + constexpr auto v1 = []() + { + FixedString<5> v{"012"}; + v.append({'a', 'e'}); + return v; + }(); + + static_assert(v1 == "012ae"); + static_assert(v1.size() == 5); + static_assert(v1.max_size() == 5); + } + + { + FixedString<7> v{"0123"}; + auto& self = v.append({'a', 'e'}); + EXPECT_EQ(v, "0123ae"); + EXPECT_EQ(self, v); + } +} + +TEST(FixedString, AppendStringView) +{ + { + // For off-by-one issues, make the capacity just fit + constexpr auto v1 = []() + { + FixedString<5> v{"012"}; + std::string_view s = "ae"; + v.append(s); + return v; + }(); + + static_assert(v1 == "012ae"); + static_assert(v1.size() == 5); + static_assert(v1.max_size() == 5); + } + + { + FixedString<7> v{"0123"}; + std::string_view s = "ae"; + auto& self = v.append(s); + EXPECT_EQ(v, "0123ae"); + EXPECT_EQ(self, v); + } +} + +TEST(FixedString, OperatorPlusEqual) +{ + constexpr auto v1 = []() + { + FixedString<17> v{"012"}; + v.append("abc"); + v.append({'d', 'e'}); + std::string_view s = "fg"; + v.append(s); + return v; + }(); + + static_assert(v1 == "012abcdefg"); + static_assert(v1.size() == 10); + static_assert(v1.max_size() == 17); +} + TEST(FixedString, Equality) { constexpr auto v1 = FixedString<12>{"012"}; From f1e4ee99c61f3248a80dfd3c5640c1954919d86b Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 15:49:52 -0700 Subject: [PATCH 20/25] [FixedString] Implement ==, <=>, compare --- include/fixed_containers/fixed_string.hpp | 28 +++- test/fixed_string_test.cpp | 177 +++++++++++++++++++++- 2 files changed, 203 insertions(+), 2 deletions(-) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index f8a63dc7..a65d7719 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -374,6 +374,11 @@ class FixedString return append(t, std_transition::source_location::current()); } + [[nodiscard]] constexpr int compare(std::string_view view) const + { + return std::string_view(*this).compare(view); + } + template constexpr bool operator==(const FixedString& other) const @@ -382,7 +387,26 @@ class FixedString } constexpr bool operator==(const CharT* other) const { - return static_cast(*this) == std::string_view{other}; + return as_view() == std::string_view{other}; + } + constexpr bool operator==(std::string_view view) const noexcept { return as_view() == view; } + + template + constexpr std::strong_ordering operator<=>( + const FixedString& other) const noexcept + { + return as_view() <=> other; + } + constexpr std::strong_ordering operator<=>(const CharT* other) const noexcept + { + return as_view() <=> std::string_view{other}; + } + constexpr std::strong_ordering operator<=>(const std::string_view& other) const noexcept + { + return as_view() <=> other; + } + } private: @@ -404,6 +428,8 @@ class FixedString } constexpr void null_terminate_at_max_length() { null_terminate(MAXIMUM_LENGTH); } + [[nodiscard]] constexpr std::string_view as_view() const { return *this; } + constexpr const FixedVecStorage& vec() const { return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; } constexpr FixedVecStorage& vec() { return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; } }; diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 0b8cf9a0..5baf2346 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -1174,13 +1174,188 @@ TEST(FixedString, Equality) const_span_of_const_ref(v2); } -TEST(FixedString, Equality_ConstCharPointer) +TEST(FixedString, Equality_NonFixedString) { static_assert(FixedString<11>{"012"} == "012"); static_assert("012" == FixedString<11>{"012"}); static_assert(FixedString<11>{"012"} != "0123"); static_assert("0123" != FixedString<11>{"012"}); + + static_assert(FixedString<11>{"012"} == std::string_view{"012"}); + static_assert(std::string_view{"012"} == FixedString<11>{"012"}); + + static_assert(FixedString<11>{"012"} != std::string_view{"0123"}); + static_assert(std::string_view{"0123"} != FixedString<11>{"012"}); +} + +TEST(FixedString, Spaceship_OverloadResolution) +{ + static_assert((FixedString<5>{"012"} <=> FixedString<11>{"012"}) == + std::strong_ordering::equal); + + static_assert((FixedString<11>{"012"} <=> "012") == std::strong_ordering::equal); + static_assert(("012" <=> FixedString<11>{"012"}) == std::strong_ordering::equal); + + static_assert((FixedString<11>{"012"} <=> std::string_view{"012"}) == + std::strong_ordering::equal); + static_assert((std::string_view{"012"} <=> FixedString<11>{"012"}) == + std::strong_ordering::equal); +} + +TEST(FixedString, Comparison) +{ + // Using ASSERT_TRUE for symmetry with static_assert + + // Equal size, left < right + { + std::string left{"123"}; + std::string right{"124"}; + + ASSERT_TRUE(left < right); + ASSERT_TRUE(left <= right); + ASSERT_TRUE(!(left > right)); + ASSERT_TRUE(!(left >= right)); + + ASSERT_TRUE(left.compare(right) < 0); + } + + { + constexpr FixedString<5> left{"123"}; + constexpr FixedString<5> right{"124"}; + + static_assert(left < right); + static_assert(left <= right); + static_assert(!(left > right)); + static_assert(!(left >= right)); + + ASSERT_TRUE(left < right); + ASSERT_TRUE(left <= right); + ASSERT_TRUE(!(left > right)); + ASSERT_TRUE(!(left >= right)); + + ASSERT_TRUE(left.compare(right) < 0); + } + + // Left has fewer elements, left > right + { + std::string left{"15"}; + std::string right{"124"}; + + ASSERT_TRUE(!(left < right)); + ASSERT_TRUE(!(left <= right)); + ASSERT_TRUE(left > right); + ASSERT_TRUE(left >= right); + + ASSERT_TRUE(left.compare(right) > 0); + } + + { + constexpr FixedString<5> left{"15"}; + constexpr FixedString<5> right{"124"}; + + static_assert(!(left < right)); + static_assert(!(left <= right)); + static_assert(left > right); + static_assert(left >= right); + + ASSERT_TRUE(!(left < right)); + ASSERT_TRUE(!(left <= right)); + ASSERT_TRUE(left > right); + ASSERT_TRUE(left >= right); + + ASSERT_TRUE(left.compare(right) > 0); + } + + // Right has fewer elements, left < right + { + std::string left{"123"}; + std::string right{"15"}; + + ASSERT_TRUE(left < right); + ASSERT_TRUE(left <= right); + ASSERT_TRUE(!(left > right)); + ASSERT_TRUE(!(left >= right)); + + ASSERT_TRUE(left.compare(right) < 0); + } + + { + constexpr FixedString<5> left{"123"}; + constexpr FixedString<5> right{"15"}; + + static_assert(left < right); + static_assert(left <= right); + static_assert(!(left > right)); + static_assert(!(left >= right)); + + ASSERT_TRUE(left < right); + ASSERT_TRUE(left <= right); + ASSERT_TRUE(!(left > right)); + ASSERT_TRUE(!(left >= right)); + + ASSERT_TRUE(left.compare(right) < 0); + } + + // Left has one additional element + { + std::string left{"123"}; + std::string right{"12"}; + + ASSERT_TRUE(!(left < right)); + ASSERT_TRUE(!(left <= right)); + ASSERT_TRUE(left > right); + ASSERT_TRUE(left >= right); + + ASSERT_TRUE(left.compare(right) > 0); + } + + { + constexpr FixedString<5> left{"123"}; + constexpr FixedString<5> right{"12"}; + + static_assert(!(left < right)); + static_assert(!(left <= right)); + static_assert(left > right); + static_assert(left >= right); + + ASSERT_TRUE(!(left < right)); + ASSERT_TRUE(!(left <= right)); + ASSERT_TRUE(left > right); + ASSERT_TRUE(left >= right); + + ASSERT_TRUE(left.compare(right) > 0); + } + + // Right has one additional element + { + std::string left{"12"}; + std::string right{"123"}; + + ASSERT_TRUE(left < right); + ASSERT_TRUE(left <= right); + ASSERT_TRUE(!(left > right)); + ASSERT_TRUE(!(left >= right)); + + ASSERT_TRUE(left.compare(right) < 0); + } + + { + constexpr FixedString<5> left{"12"}; + constexpr FixedString<5> right{"123"}; + + static_assert(left < right); + static_assert(left <= right); + static_assert(!(left > right)); + static_assert(!(left >= right)); + + ASSERT_TRUE(left < right); + ASSERT_TRUE(left <= right); + ASSERT_TRUE(!(left > right)); + ASSERT_TRUE(!(left >= right)); + + ASSERT_TRUE(left.compare(right) < 0); + } } } // namespace fixed_containers From c8d8a85107c610437e1ed2560164f329cbbdf146 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 15:57:02 -0700 Subject: [PATCH 21/25] [FixedString] Implement starts_with(), ends_with(), substr() --- include/fixed_containers/fixed_string.hpp | 36 +++++++++++++++++ test/fixed_string_test.cpp | 49 +++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index a65d7719..11f808dc 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -407,6 +407,42 @@ class FixedString return as_view() <=> other; } + [[nodiscard]] constexpr bool starts_with(const std::string_view& prefix) const noexcept + { + return as_view().starts_with(prefix); + } + [[nodiscard]] constexpr bool starts_with(char x) const noexcept + { + return as_view().starts_with(x); + } + + [[nodiscard]] constexpr bool starts_with(const char* x) const noexcept + { + return as_view().starts_with(x); + } + + [[nodiscard]] constexpr bool ends_with(const std::string_view& suffix) const noexcept + { + return as_view().ends_with(suffix); + } + [[nodiscard]] constexpr bool ends_with(char x) const noexcept { return as_view().ends_with(x); } + [[nodiscard]] constexpr bool ends_with(const char* x) const noexcept + { + return as_view().ends_with(x); + } + + [[nodiscard]] constexpr std::string_view substr( + size_type pos = 0, + size_t len = MAXIMUM_LENGTH, + const std_transition::source_location& loc = + std_transition::source_location::current()) const + { + if (preconditions::test(pos < length())) + { + Checking::out_of_range(pos, length(), loc); + } + + return std::string_view(*this).substr(pos, len); } private: diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 5baf2346..39a26c0d 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -1358,4 +1358,53 @@ TEST(FixedString, Comparison) } } +TEST(FixedString, StartsWith) +{ + constexpr auto v1 = []() + { + FixedString<7> v{"0123"}; + return v; + }(); + + static_assert(v1.starts_with('0')); + static_assert(v1.starts_with("01")); + static_assert(v1.starts_with(std::string_view{"012"})); + + static_assert(!v1.starts_with('1')); + static_assert(!v1.starts_with("1")); + static_assert(!v1.starts_with(std::string_view{"12"})); +} + +TEST(FixedString, EndsWith) +{ + constexpr auto v1 = []() + { + FixedString<7> v{"0123"}; + return v; + }(); + + static_assert(v1.ends_with('3')); + static_assert(v1.ends_with("23")); + static_assert(v1.ends_with(std::string_view{"123"})); + + static_assert(!v1.ends_with('2')); + static_assert(!v1.ends_with("2")); + static_assert(!v1.ends_with(std::string_view{"12"})); +} + +TEST(FixedString, Substring) +{ + constexpr auto v1 = []() + { + FixedString<7> v{"0123"}; + return v; + }(); + + static_assert(v1.substr(0, 3) == "012"); + static_assert(v1.substr(1, 2) == "12"); + static_assert(v1.substr(2, 2) == "23"); + + EXPECT_DEATH((void)v1.substr(5, 1), ""); +} + } // namespace fixed_containers From 2eb29a15a5d09c0d608224d0ba7d9081b6731056 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 16:16:44 -0700 Subject: [PATCH 22/25] [FixedString] Implement resize() --- include/fixed_containers/fixed_string.hpp | 16 +++++++ test/fixed_string_test.cpp | 58 +++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index 11f808dc..7ec9dbe2 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -445,6 +445,22 @@ class FixedString return std::string_view(*this).substr(pos, len); } + constexpr void resize( + size_type count, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + resize(count, CharT{}, loc); + } + + constexpr void resize( + size_type count, + CharT ch, + const std_transition::source_location& loc = std_transition::source_location::current()) + { + vec().resize(count, ch, loc); + null_terminate(loc); + } + private: constexpr void null_terminate(std::size_t n) { diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 39a26c0d..7b5c77e8 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -1407,4 +1407,62 @@ TEST(FixedString, Substring) EXPECT_DEATH((void)v1.substr(5, 1), ""); } +TEST(FixedString, Resize) +{ + constexpr auto v1 = []() + { + FixedString<7> v{"012"}; + v.resize(6); + v[4] = 'a'; + return v; + }(); + + static_assert(v1[0] == '0'); + static_assert(v1[1] == '1'); + static_assert(v1[2] == '2'); + static_assert(v1[3] == '\0'); + static_assert(v1[4] == 'a'); + static_assert(v1[5] == '\0'); + static_assert(v1.size() == 6); + static_assert(v1.max_size() == 7); + + constexpr auto v2 = []() + { + FixedString<7> v{"012"}; + v.resize(7, 'c'); + v[4] = 'x'; + v.resize(5, 'e'); + return v; + }(); + + static_assert(v2[0] == '0'); + static_assert(v2[1] == '1'); + static_assert(v2[2] == '2'); + static_assert(v2[3] == 'c'); + static_assert(v2[4] == 'x'); + static_assert(v2.size() == 5); + static_assert(v2.max_size() == 7); + + FixedString<8> v3{"0123"}; + v3.resize(6); + + EXPECT_TRUE(std::ranges::equal(v3, std::array{'0', '1', '2', '3', '\0', '\0'})); + + v3.resize(2); + EXPECT_EQ(v3, "01"); + + v3.resize(5, '3'); + EXPECT_EQ(v3, "01333"); +} + +TEST(FixedString, Resize_ExceedCapacity) +{ + FixedString<3> v1{}; + EXPECT_DEATH(v1.resize(6), ""); + EXPECT_DEATH(v1.resize(6, 5), ""); + const size_t to_size = 7; + EXPECT_DEATH(v1.resize(to_size), ""); + EXPECT_DEATH(v1.resize(to_size, 5), ""); +} + } // namespace fixed_containers From d1874f09ca676c265a943b36a7d19d57723d8bd6 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 21:34:05 -0700 Subject: [PATCH 23/25] [Vector/Deque<->String consistency] test formatting --- test/fixed_deque_test.cpp | 1 - test/fixed_vector_test.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/test/fixed_deque_test.cpp b/test/fixed_deque_test.cpp index b9543f46..ae9db715 100644 --- a/test/fixed_deque_test.cpp +++ b/test/fixed_deque_test.cpp @@ -1047,7 +1047,6 @@ TEST(FixedDeque, Resize) EXPECT_TRUE(std::ranges::equal(v3, std::array{{0, 1}})); v3.resize(5, 3); - EXPECT_TRUE(std::ranges::equal(v3, std::array{{0, 1, 3, 3, 3}})); { diff --git a/test/fixed_vector_test.cpp b/test/fixed_vector_test.cpp index 428e4d0a..49e25049 100644 --- a/test/fixed_vector_test.cpp +++ b/test/fixed_vector_test.cpp @@ -1162,7 +1162,6 @@ TEST(FixedVector, Resize) EXPECT_TRUE(std::ranges::equal(v3, std::array{{0, 1}})); v3.resize(5, 3); - EXPECT_TRUE(std::ranges::equal(v3, std::array{{0, 1, 3, 3, 3}})); { From eaa9ecb465f2cba7d822e9c963ca7961b55284c8 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 16:57:06 -0700 Subject: [PATCH 24/25] [FixedString] Implement is_full() and make_fixed_string() --- include/fixed_containers/fixed_string.hpp | 46 +++++++++++++++++++++++ test/fixed_string_test.cpp | 31 +++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp index 7ec9dbe2..0e1599de 100644 --- a/include/fixed_containers/fixed_string.hpp +++ b/include/fixed_containers/fixed_string.hpp @@ -485,6 +485,52 @@ class FixedString constexpr const FixedVecStorage& vec() const { return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; } constexpr FixedVecStorage& vec() { return IMPLEMENTATION_DETAIL_DO_NOT_USE_data_; } }; + +template +constexpr typename FixedString::size_type is_full( + const FixedString& c) +{ + return c.size() >= c.max_size(); +} + +/** + * Construct a FixedString with its capacity being deduced from the number of items being passed. + */ +template < + fixed_string_customize::FixedStringChecking CheckingType, + std::size_t MAXIMUM_LENGTH_WITH_NULL_TERMINATOR, + // Exposing this as a template parameter is useful for customization (for example with + // child classes that set the CheckingType) + typename FixedStringType = FixedString> +[[nodiscard]] constexpr FixedStringType make_fixed_string( + const char (&list)[MAXIMUM_LENGTH_WITH_NULL_TERMINATOR], + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept +{ + constexpr std::size_t MAXIMUM_LENGTH = MAXIMUM_LENGTH_WITH_NULL_TERMINATOR - 1; + assert(list[MAXIMUM_LENGTH] == '\0'); + FixedStringType s{}; + s.resize(MAXIMUM_LENGTH, loc); + for (std::size_t i = 0; i < MAXIMUM_LENGTH; i++) + { + s[i] = list[i]; + } + return s; +} + +template +[[nodiscard]] constexpr auto make_fixed_string( + const char (&list)[MAXIMUM_LENGTH_WITH_NULL_TERMINATOR], + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept +{ + constexpr std::size_t MAXIMUM_LENGTH = MAXIMUM_LENGTH_WITH_NULL_TERMINATOR - 1; + using CheckingType = fixed_string_customize::AbortChecking; + using FixedStringType = FixedString; + return make_fixed_string( + list, loc); +} + } // namespace fixed_containers namespace std diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index 7b5c77e8..db966004 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -1465,4 +1465,35 @@ TEST(FixedString, Resize_ExceedCapacity) EXPECT_DEATH(v1.resize(to_size, 5), ""); } +TEST(FixedString, Full) +{ + constexpr auto v1 = []() + { + FixedString<4> v{}; + v.push_back('0'); + v.push_back('1'); + v.push_back('2'); + v.push_back('3'); + return v; + }(); + + static_assert(v1 == "0123"); + static_assert(is_full(v1)); + static_assert(v1.size() == 4); + static_assert(v1.max_size() == 4); + + EXPECT_TRUE(is_full(v1)); +} + +TEST(FixedString, MaxSizeDeduction) +{ + constexpr auto v1 = make_fixed_string("abcde"); + static_assert(v1.size() == 5); + static_assert(v1.max_size() == 5); + static_assert(v1[0] == 'a'); + static_assert(v1[1] == 'b'); + static_assert(v1[2] == 'c'); + static_assert(v1[3] == 'd'); + static_assert(v1[4] == 'e'); +} } // namespace fixed_containers From 0fc561a4dac2507bee7d725e6da9d98e7e9fb667 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 26 Sep 2023 16:57:35 -0700 Subject: [PATCH 25/25] [FixedString] Add test for CTAD, ADL and instance usage in templates --- test/fixed_string_test.cpp | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp index db966004..6cd614a2 100644 --- a/test/fixed_string_test.cpp +++ b/test/fixed_string_test.cpp @@ -1496,4 +1496,43 @@ TEST(FixedString, MaxSizeDeduction) static_assert(v1[3] == 'd'); static_assert(v1[4] == 'e'); } + +TEST(FixedString, ClassTemplateArgumentDeduction) +{ + // Compile-only test + FixedString a = FixedString<5>{}; + (void)a; +} + +namespace +{ +template /*MY_STR*/> +struct FixedStringInstanceCanBeUsedAsATemplateParameter +{ +}; + +template /*MY_STR*/> +constexpr void fixed_string_instance_can_be_used_as_a_template_parameter() +{ +} +} // namespace + +TEST(FixedString, UsageAsTemplateParameter) +{ + static constexpr FixedString<5> MY_STR1{}; + fixed_string_instance_can_be_used_as_a_template_parameter(); + FixedStringInstanceCanBeUsedAsATemplateParameter my_struct{}; + static_cast(my_struct); +} + } // namespace fixed_containers + +namespace another_namespace_unrelated_to_the_fixed_containers_namespace +{ +TEST(FixedString, ArgumentDependentLookup) +{ + // Compile-only test + fixed_containers::FixedString<5> a{}; + is_full(a); +} +} // namespace another_namespace_unrelated_to_the_fixed_containers_namespace