Skip to content

Commit

Permalink
Introduce FixedString with basic functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkaratarakis committed Jul 20, 2023
1 parent f761e0e commit 791ecc5
Show file tree
Hide file tree
Showing 4 changed files with 376 additions and 0 deletions.
26 changes: 26 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down Expand Up @@ -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"],
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
153 changes: 153 additions & 0 deletions include/fixed_containers/fixed_string.hpp
Original file line number Diff line number Diff line change
@@ -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 <array>
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <string_view>

namespace fixed_containers::fixed_string_customize
{
template <class T>
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 <std::size_t /*MAXIMUM_LENGTH*/>
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 <std::size_t MAXIMUM_LENGTH,
fixed_string_customize::FixedStringChecking CheckingType =
fixed_string_customize::AbortChecking<MAXIMUM_LENGTH>>
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<char, MAXIMUM_LENGTH + 1> 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 <std::size_t MAXIMUM_LENGTH,
fixed_containers::fixed_string_customize::FixedStringChecking CheckingType>
struct tuple_size<fixed_containers::fixed_string_detail::FixedString<MAXIMUM_LENGTH, CheckingType>>
: std::integral_constant<std::size_t, 0>
{
static_assert(fixed_containers::AlwaysFalseV<decltype(MAXIMUM_LENGTH), CheckingType>,
"Implicit Structured Binding due to the fields being public is disabled");
};

} // namespace std
195 changes: 195 additions & 0 deletions test/fixed_string_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#include "fixed_containers/fixed_string.hpp"

#include "fixed_containers/concepts.hpp"
#include "fixed_containers/consteval_compare.hpp"

#include <gtest/gtest.h>

namespace fixed_containers::fixed_string_detail
{
namespace
{
using FixedStringType = FixedString<5>;
// Static assert for expected type properties
static_assert(TriviallyCopyable<FixedStringType>);
static_assert(NotTrivial<FixedStringType>);
static_assert(StandardLayout<FixedStringType>);
static_assert(IsStructuralType<FixedStringType>);
} // 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<void>(v3.at(5)), "");
EXPECT_DEATH(static_cast<void>(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

0 comments on commit 791ecc5

Please sign in to comment.