diff --git a/BUILD.bazel b/BUILD.bazel index a0df02e1..b68ab985 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -169,6 +169,19 @@ cc_library( copts = ["-std=c++20"], ) +cc_library( + name = "fixed_string", + hdrs = ["include/fixed_containers/fixed_string.hpp"], + includes = ["include"], + deps = [ + ":concepts", + ":preconditions", + ":source_location", + ":string_literal", + ], + copts = ["-std=c++20"], +) + cc_library( name = "fixed_vector", hdrs = ["include/fixed_containers/fixed_vector.hpp"], @@ -512,6 +525,19 @@ cc_test( copts = ["-std=c++20"], ) +cc_test( + name = "fixed_string_test", + srcs = ["test/fixed_string_test.cpp"], + deps = [ + ":concepts", + ":consteval_compare", + ":fixed_string", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], + copts = ["-std=c++20"], +) + cc_test( name = "fixed_vector_test", srcs = ["test/fixed_vector_test.cpp"], diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c826b2d..ff1089a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,6 +152,8 @@ if(BUILD_TESTS) add_test_dependencies(fixed_red_black_tree_view_test) add_executable(fixed_set_test test/fixed_set_test.cpp) add_test_dependencies(fixed_set_test) + add_executable(fixed_string_test test/fixed_string_test.cpp) + add_test_dependencies(fixed_string_test) add_executable(fixed_vector_test test/fixed_vector_test.cpp) add_test_dependencies(fixed_vector_test) add_executable(in_out_test test/in_out_test.cpp) diff --git a/include/fixed_containers/fixed_string.hpp b/include/fixed_containers/fixed_string.hpp new file mode 100644 index 00000000..2853056b --- /dev/null +++ b/include/fixed_containers/fixed_string.hpp @@ -0,0 +1,153 @@ +#pragma once + +#include "fixed_containers/concepts.hpp" +#include "fixed_containers/preconditions.hpp" +#include "fixed_containers/source_location.hpp" +#include "fixed_containers/string_literal.hpp" + +#include +#include +#include +#include +#include + +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 +}; + +template +struct AbortChecking +{ + [[noreturn]] static constexpr void out_of_range(const std::size_t /*index*/, + const std::size_t /*size*/, + const std_transition::source_location& /*loc*/) + { + std::abort(); + } +}; +} // namespace fixed_containers::fixed_string_customize + +namespace fixed_containers::fixed_string_detail +{ +template > +class FixedString +{ + using Checking = CheckingType; + +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&; + +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_; + +public: + constexpr FixedString() noexcept + : IMPLEMENTATION_DETAIL_DO_NOT_USE_length_{} + , IMPLEMENTATION_DETAIL_DO_NOT_USE_data_{} + { + null_terminate(); + } + + explicit(false) constexpr FixedString(const std::string_view& view) noexcept + : FixedString{} + { + 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(); + } + + [[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()); + } + [[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()); + } + + [[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); + } + [[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); + } + + [[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 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 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(); } + + explicit(false) constexpr operator std::string_view() const + { + return std::string_view(data(), length()); + } + +private: + constexpr void null_terminate() { IMPLEMENTATION_DETAIL_DO_NOT_USE_data_.at(length()) = '\0'; } +}; +} // namespace fixed_containers::fixed_string_detail + +namespace std +{ +template +struct tuple_size> + : std::integral_constant +{ + static_assert(fixed_containers::AlwaysFalseV, + "Implicit Structured Binding due to the fields being public is disabled"); +}; + +} // namespace std diff --git a/test/fixed_string_test.cpp b/test/fixed_string_test.cpp new file mode 100644 index 00000000..50e98134 --- /dev/null +++ b/test/fixed_string_test.cpp @@ -0,0 +1,195 @@ +#include "fixed_containers/fixed_string.hpp" + +#include "fixed_containers/concepts.hpp" +#include "fixed_containers/consteval_compare.hpp" + +#include + +namespace fixed_containers::fixed_string_detail +{ +namespace +{ +using FixedStringType = FixedString<5>; +// Static assert for expected type properties +static_assert(TriviallyCopyable); +static_assert(NotTrivial); +static_assert(StandardLayout); +static_assert(IsStructuralType); +} // namespace + +TEST(FixedString, DefaultConstructor) +{ + constexpr FixedString<8> v1{}; + static_assert(v1.empty()); + static_assert(v1.max_size() == 8); +} + +TEST(FixedString, StringViewConstructor) +{ + constexpr std::string_view STRING_VIEW{"123456789"}; + + constexpr FixedString<17> v1{STRING_VIEW}; + static_assert(!v1.empty()); + static_assert(v1.size() == 9); + static_assert(v1.max_size() == 17); +} + +TEST(FixedString, BracketOperator) +{ + constexpr auto v1 = []() + { + FixedString<11> v{"aaa"}; + // v.resize(3); + v[0] = '0'; + v[1] = '1'; + v[2] = '2'; + v[1] = 'b'; + + return v; + }(); + + static_assert(v1[0] == '0'); + static_assert(v1[1] == 'b'); + static_assert(v1[2] == '2'); + static_assert(v1.size() == 3); + + auto v2 = FixedString<11>{"012"}; + v2[1] = 'b'; + EXPECT_EQ(v2[0], '0'); + EXPECT_EQ(v2[1], 'b'); + EXPECT_EQ(v2[2], '2'); + + const auto& v3 = v2; + EXPECT_EQ(v3[0], '0'); + EXPECT_EQ(v3[1], 'b'); + EXPECT_EQ(v3[2], '2'); +} + +TEST(FixedString, At) +{ + constexpr auto v1 = []() + { + FixedString<11> v{"012"}; + // v.resize(3); + v.at(0) = '0'; + v.at(1) = '1'; + v.at(2) = '2'; + 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); + + auto v2 = FixedString<11>{"012"}; + v2.at(1) = 'b'; + EXPECT_EQ(v2.at(0), '0'); + EXPECT_EQ(v2.at(1), 'b'); + EXPECT_EQ(v2.at(2), '2'); + + const auto& v3 = v2; + EXPECT_EQ(v3.at(0), '0'); + EXPECT_EQ(v3.at(1), 'b'); + EXPECT_EQ(v3.at(2), '2'); +} + +TEST(FixedString, At_OutOfBounds) +{ + auto v2 = FixedString<11>{"012"}; + EXPECT_DEATH(v2.at(3) = 'z', ""); + EXPECT_DEATH(v2.at(v2.size()) = 'z', ""); + + const auto& v3 = v2; + EXPECT_DEATH(static_cast(v3.at(5)), ""); + EXPECT_DEATH(static_cast(v3.at(v2.size())), ""); +} + +TEST(FixedString, CapacityAndMaxSize) +{ + { + constexpr FixedString<3> v1{}; + static_assert(v1.capacity() == 3); + static_assert(v1.max_size() == 3); + } + + { + FixedString<3> v1{}; + EXPECT_EQ(3, v1.capacity()); + EXPECT_EQ(3, v1.max_size()); + } +} + +TEST(FixedString, LengthAndSize) +{ + { + constexpr auto v1 = []() { return FixedString<7>{}; }(); + static_assert(v1.length() == 0); + static_assert(v1.size() == 0); + static_assert(v1.max_size() == 7); + } + + { + constexpr auto v1 = []() { return FixedString<7>{"123"}; }(); + static_assert(v1.length() == 3); + static_assert(v1.size() == 3); + static_assert(v1.max_size() == 7); + } +} + +TEST(FixedString, Empty) +{ + constexpr auto v1 = []() { return FixedString<7>{}; }(); + + static_assert(v1.empty()); + static_assert(v1.max_size() == 7); +} + +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, 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'); + + EXPECT_EQ(*std::next(v1.data(), 0), '0'); + EXPECT_EQ(*std::next(v1.data(), 1), '1'); + EXPECT_EQ(*std::next(v1.data(), 2), '2'); + + 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 + } +} + +} // namespace fixed_containers::fixed_string_detail