Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce FixedString with basic functionality #54

Merged
merged 1 commit into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading