-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce FixedString with basic functionality
- Loading branch information
1 parent
f761e0e
commit 791ecc5
Showing
4 changed files
with
376 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |