Skip to content

Commit 3af7852

Browse files
committed
tests and docs
1 parent 75137e4 commit 3af7852

File tree

6 files changed

+258
-27
lines changed

6 files changed

+258
-27
lines changed

.github/workflows/code_testing.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ jobs:
8282
- name: Run examples
8383
working-directory: build
8484
run: |
85-
for example in examples/examples_*; do
85+
for example in examples/example*; do
8686
echo "executing ${example}"
8787
./${example}
8888
done

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ It contains:
1919
- `channel`: A single producer, single consumer queue
2020
- `variant2`: Like `std::variant` but optimized for exactly two types
2121
- `mutex`/`shared_mutex`: Rust inspired mutex interfaces that hold their data instead of living next to it
22+
- `static_string`: A string type that is smaller than `std::string` for use cases where you do not need to resize the string
2223

2324
## Usage
2425

@@ -113,6 +114,10 @@ Rust inspired mutex interfaces that hold their data instead of living next to it
113114
The benefit of this approach is that it makes it harder (impossible in rust) to access the
114115
data without holding the mutex.
115116

117+
### `static_string`
118+
A string type that is small than `std::string` but does not have the ability to grow or shrink.
119+
This is useful if you never need to resize the string and want to keep the memory footprint low.
120+
116121
### Further Examples
117122

118123
Compilable code examples can be found in [examples](./examples). The example build requires the cmake

examples/example_static_string.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
#include <dice/template-library/static_string.hpp>
22

3+
#include <cassert>
4+
#include <iostream>
5+
#include <string>
36

47
int main() {
5-
// TODO
6-
}
8+
assert(sizeof(dice::template_library::static_string) < sizeof(std::string));
9+
10+
dice::template_library::static_string str{"Hello World"};
11+
std::cout << str << std::endl;
12+
}

include/dice/template-library/static_string.hpp

+47-24
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
#define DICE_TEMPLATELIBRARY_CONSTSTRING_HPP
33

44
#include <cassert>
5+
#include <cstring>
6+
#include <memory>
7+
#include <ostream>
58
#include <string_view>
69
#include <utility>
7-
#include <memory>
810

911
namespace dice::template_library {
1012

@@ -38,6 +40,21 @@ namespace dice::template_library {
3840
swap(a.size_, b.size_);
3941
}
4042

43+
void copy_assign_from_other(basic_static_string const &other) {
44+
if (size_ != 0) {
45+
std::allocator_traits<allocator_type>::deallocate(alloc_, data_, size_);
46+
}
47+
48+
size_ = other.size_;
49+
50+
if (size_ != 0) {
51+
data_ = std::allocator_traits<allocator_type>::allocate(alloc_, size_);
52+
memcpy(std::to_address(data_), std::to_address(other.data_), size_);
53+
} else {
54+
data_ = nullptr;
55+
}
56+
}
57+
4158
public:
4259
constexpr basic_static_string(allocator_type const &alloc = allocator_type{}) noexcept
4360
: data_{nullptr}, size_{0}, alloc_{alloc} {
@@ -55,22 +72,35 @@ namespace dice::template_library {
5572
memcpy(std::to_address(data_), sv.data(), size_);
5673
}
5774

58-
basic_static_string(basic_static_string const &) = delete;
59-
basic_static_string &operator=(basic_static_string const &) = delete;
75+
basic_static_string(basic_static_string const &other) : basic_static_string{static_cast<view_type>(other), other.alloc_} {
76+
}
77+
78+
basic_static_string &operator=(basic_static_string const &other) {
79+
if (this == &other) {
80+
return *this;
81+
}
82+
83+
if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value) {
84+
alloc_ = other.alloc_;
85+
}
86+
87+
copy_assign_from_other(other);
88+
return *this;
89+
}
6090

6191
constexpr basic_static_string(basic_static_string &&other) noexcept : data_{std::exchange(other.data_, nullptr)},
6292
size_{std::exchange(other.size_, 0)},
6393
alloc_{std::move(other.alloc_)} {
6494
}
6595

66-
basic_static_string &operator=(basic_static_string &&other) noexcept(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value
67-
|| std::allocator_traits<Allocator>::is_always_equal::value) {
96+
basic_static_string &operator=(basic_static_string &&other) noexcept(std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value
97+
|| std::allocator_traits<allocator_type>::is_always_equal::value) {
6898
assert(this != &other);
6999

70-
if constexpr (std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value) {
100+
if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value) {
71101
swap(*this, other);
72102
return *this;
73-
} else if constexpr (std::allocator_traits<Allocator>::is_always_equal::value) {
103+
} else if constexpr (std::allocator_traits<allocator_type>::is_always_equal::value) {
74104
swap_data(*this, other);
75105
return *this;
76106
} else {
@@ -80,20 +110,7 @@ namespace dice::template_library {
80110
}
81111

82112
// alloc_ != other.alloc_ and not allowed to propagate, need to copy
83-
84-
if (size_ != 0) {
85-
std::allocator_traits<allocator_type>::deallocate(alloc_, data_, size_);
86-
}
87-
88-
size_ = other.size_;
89-
90-
if (size_ != 0) {
91-
data_ = std::allocator_traits<allocator_type>::allocate(alloc_, size_);
92-
memcpy(std::to_address(data_), std::to_address(other.data_), size_);
93-
} else {
94-
data_ = nullptr;
95-
}
96-
113+
copy_assign_from_other(other);
97114
return *this;
98115
}
99116
}
@@ -115,20 +132,20 @@ namespace dice::template_library {
115132
return data_;
116133
}
117134

118-
[[nodiscard]] size_type empty() const noexcept {
135+
[[nodiscard]] bool empty() const noexcept {
119136
return size_ == 0;
120137
}
121138

122139
[[nodiscard]] size_type size() const noexcept {
123140
return size_;
124141
}
125142

126-
[[nodiscard]] value_type operator[](size_type ix) const noexcept {
143+
[[nodiscard]] value_type operator[](size_type const ix) const noexcept {
127144
assert(ix < size());
128145
return std::to_address(data_)[ix];
129146
}
130147

131-
[[nodiscard]] value_type &operator[](size_type ix) noexcept {
148+
[[nodiscard]] value_type &operator[](size_type const ix) noexcept {
132149
assert(ix < size());
133150
return std::to_address(data_)[ix];
134151
}
@@ -219,6 +236,12 @@ namespace dice::template_library {
219236
}
220237
};
221238

239+
template<typename Char, typename CharTraits, typename Allocator>
240+
std::basic_ostream<Char, CharTraits> &operator<<(std::basic_ostream<Char, CharTraits> &os, basic_static_string<Char, CharTraits, Allocator> const &str) {
241+
os << static_cast<std::basic_string_view<Char, CharTraits>>(str);
242+
return os;
243+
}
244+
222245
using static_string = basic_static_string<char>;
223246
using static_wstring = basic_static_string<wchar_t>;
224247
using static_u8string = basic_static_string<char8_t>;

tests/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,5 @@ custom_add_test(tests_mutex)
7676
add_executable(tests_shared_mutex tests_shared_mutex.cpp)
7777
custom_add_test(tests_shared_mutex)
7878

79+
add_executable(tests_static_string tests_static_string.cpp)
80+
custom_add_test(tests_static_string)

tests/tests_static_string.cpp

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
2+
#include <doctest/doctest.h>
3+
4+
#include <dice/template-library/static_string.hpp>
5+
6+
#include <ranges>
7+
8+
TEST_SUITE("static_string") {
9+
using namespace dice::template_library;
10+
11+
template<typename T>
12+
struct weird_allocator : std::allocator<T> {
13+
using is_always_equal = std::false_type;
14+
using propagate_on_container_move_assignment = std::false_type;
15+
16+
bool operator==([[maybe_unused]] weird_allocator const &other) const noexcept {
17+
return false;
18+
}
19+
};
20+
21+
using weird_static_string = basic_static_string<char, std::char_traits<char>, weird_allocator<char>>;
22+
23+
static_assert(sizeof(static_string) == 2 * sizeof(void *));
24+
25+
template<typename Allocator>
26+
void check_empty_string(basic_static_string<char, std::char_traits<char>, Allocator> const &s) {
27+
CHECK_EQ(s.data(), nullptr);
28+
CHECK_EQ(s.size(), 0);
29+
CHECK(s.empty());
30+
CHECK_EQ(s.begin(), s.end());
31+
CHECK_EQ(s.rbegin(), s.rend());
32+
CHECK_EQ(s.cbegin(), s.cend());
33+
CHECK_EQ(s.crbegin(), s.crend());
34+
CHECK_EQ(s, "");
35+
CHECK_EQ(static_cast<std::string_view>(s), "");
36+
}
37+
38+
template<typename Allocator>
39+
void check_non_empty_string(basic_static_string<char, std::char_traits<char>, Allocator> const &actual, std::string_view expected) {
40+
assert(!expected.empty());
41+
42+
CHECK_EQ(actual.size(), expected.size());
43+
CHECK_EQ(actual, actual);
44+
CHECK_EQ(actual, expected);
45+
CHECK_EQ(actual <=> expected, std::strong_ordering::equal);
46+
CHECK_EQ(actual <=> actual, std::strong_ordering::equal);
47+
CHECK_FALSE(actual.empty());
48+
CHECK(std::ranges::equal(actual, expected));
49+
CHECK(std::ranges::equal(actual | std::views::reverse, expected | std::views::reverse));
50+
CHECK_EQ(memcmp(actual.data(), expected.data(), actual.size()), 0);
51+
CHECK_EQ(actual.front(), expected.front());
52+
CHECK_EQ(actual.back(), expected.back());
53+
CHECK_EQ(actual[0], expected[0]);
54+
CHECK_EQ(static_cast<std::string_view>(actual), expected);
55+
}
56+
57+
TEST_CASE("empty string") {
58+
SUBCASE("default ctor") {
59+
static_string s;
60+
check_empty_string(s);
61+
}
62+
63+
SUBCASE("string view ctor") {
64+
static_string s{""};
65+
check_empty_string(s);
66+
}
67+
}
68+
69+
TEST_CASE("move ctor") {
70+
std::string_view const expected = "Hello World";
71+
static_string s{expected};
72+
check_non_empty_string(s, expected);
73+
74+
static_string s2{std::move(s)};
75+
76+
check_empty_string(s);
77+
check_non_empty_string(s2, expected);
78+
}
79+
80+
TEST_CASE("copy ctor") {
81+
std::string_view const expected = "Hello World";
82+
83+
static_string const s{expected};
84+
check_non_empty_string(s, expected);
85+
86+
static_string const s2{s};
87+
check_non_empty_string(s, expected);
88+
check_non_empty_string(s2, expected);
89+
}
90+
91+
TEST_CASE("move assignment") {
92+
SUBCASE("regular") {
93+
SUBCASE("std allocator") {
94+
static_string s1{"Hello World"};
95+
static_string s2{"Spherical Cow"};
96+
s1 = std::move(s2);
97+
check_non_empty_string(s1, "Spherical Cow");
98+
}
99+
100+
SUBCASE("weird allocator") {
101+
weird_static_string s1{"Hello World"};
102+
weird_static_string s2{"Spherical Cow"};
103+
104+
s1 = std::move(s2);
105+
check_non_empty_string(s1, "Spherical Cow");
106+
}
107+
}
108+
109+
SUBCASE("assign empty") {
110+
SUBCASE("std alloctor") {
111+
static_string empty;
112+
check_empty_string(empty);
113+
114+
static_string s{"Hello World"};
115+
check_non_empty_string(s, "Hello World");
116+
117+
s = std::move(empty);
118+
check_empty_string(s);
119+
}
120+
121+
SUBCASE("weird allocator") {
122+
weird_static_string empty;
123+
check_empty_string(empty);
124+
125+
weird_static_string s{"Hello World"};
126+
check_non_empty_string(s, "Hello World");
127+
128+
s = std::move(empty);
129+
check_empty_string(s);
130+
}
131+
}
132+
}
133+
134+
TEST_CASE("copy assignment") {
135+
SUBCASE("regular") {
136+
SUBCASE("std allocator") {
137+
static_string s1{"Hello World"};
138+
static_string const s2{"Spherical Cow"};
139+
s1 = s2;
140+
check_non_empty_string(s1, "Spherical Cow");
141+
check_non_empty_string(s2, "Spherical Cow");
142+
}
143+
144+
SUBCASE("weird allocator") {
145+
weird_static_string s1{"Hello World"};
146+
weird_static_string const s2{"Spherical Cow"};
147+
148+
s1 = s2;
149+
check_non_empty_string(s1, "Spherical Cow");
150+
check_non_empty_string(s2, "Spherical Cow");
151+
}
152+
}
153+
154+
SUBCASE("assign empty") {
155+
SUBCASE("std alloctor") {
156+
static_string const empty;
157+
check_empty_string(empty);
158+
159+
static_string s{"Hello World"};
160+
check_non_empty_string(s, "Hello World");
161+
162+
s = empty;
163+
check_empty_string(s);
164+
check_empty_string(empty);
165+
}
166+
167+
SUBCASE("weird allocator") {
168+
weird_static_string const empty;
169+
check_empty_string(empty);
170+
171+
weird_static_string s{"Hello World"};
172+
check_non_empty_string(s, "Hello World");
173+
174+
s = empty;
175+
check_empty_string(s);
176+
check_empty_string(empty);
177+
}
178+
}
179+
}
180+
181+
TEST_CASE("swap") {
182+
std::string_view const expected1 = "Hello World";
183+
std::string_view const expected2 = "Spherical Cow";
184+
185+
static_string s1{expected1};
186+
check_non_empty_string(s1, expected1);
187+
188+
static_string s2{expected2};
189+
check_non_empty_string(s2, expected2);
190+
191+
swap(s1, s2);
192+
check_non_empty_string(s1, expected2);
193+
check_non_empty_string(s2, expected1);
194+
}
195+
}

0 commit comments

Comments
 (0)