opynsim
Unofficial C++ Documentation
Loading...
Searching...
No Matches
shared_pre_hashed_string.h
Go to the documentation of this file.
1#pragma once
2
4
5#include <atomic>
6#include <compare>
7#include <concepts>
8#include <functional>
9#include <memory>
10#include <new>
11#include <ostream>
12#include <string_view>
13#include <utility>
14
15namespace osc
16{
17 // an immutable, reference-counted, and pre-hashed string-like object
18 //
19 // key differences to `std::string`:
20 //
21 // + only one pointer in size (`std::string` tends to be 3 pointers or so)
22 // + cheap to copy
23 // + cheap to equality-compare copies
24 // + cheap to `std::hash` (as it's pre-hashed)
25 // ~ immutable
26 // ~ one branch more expensive than `std::string` to compare when not comparing a copy
27 // - always heap-allocates its representation (i.e. no SSO)
28 // - always pre-hashes the string content, even if you don't plan on using the hash
29 //
30 // key differences to `StringName`:
31 //
32 // + doesn't a global lookup
33 // - because there's no global deduplication, duplication of string data across
34 // separately-constructed instances is possible
35 //
36 // usage recommendations:
37 //
38 // - use `std::string` / `std::string_view` for almost all day-to-day string
39 // use-cases, especially if the strings are likely to be short enough for
40 // SSO and you don't plan on (re)hashing the string much
41 //
42 // - use `SharedPreHashedString` in systems that are mostly isolated in one place,
43 // use, copy, and hash a lot of potentially-longer strings in associative lookups.
44 // e.g. a standalone system that reads potentially-heavily-repeated strings from
45 // an input file, where the system could reasonably have its own
46 // `std::unordered_set<SharedPreHashedString>` or similar
47 //
48 // - use `StringName` in larger multi-level systems that use, copy, and hash a lot of
49 // potentially-longer strings in associative lookups. E.g. a large application
50 // containing a graphics system, which binds material keys to shader uniforms on the GPU;
51 // a mid-level UI controller, which maintains a datamodel containing material key-value
52 // pairs; and a high-level UI, which binds those pairs to a nodegraph UI (i.e. despite
53 // each tier having an independent responsibility, there's likely to be overlaps in
54 // data values)
56 public:
57 using traits_type = std::string_view::traits_type;
58 using value_type = std::string_view::value_type;
59 using size_type = std::string_view::size_type;
60 using difference_type = std::string_view::difference_type;
61 using reference = std::string_view::const_reference;
62 using const_reference = std::string_view::const_reference;
63 using iterator = std::string_view::const_iterator;
64 using const_iterator = std::string_view::const_iterator;
65 using reverse_iterator = std::string_view::const_reverse_iterator;
66 using const_reverse_iterator = std::string_view::const_reverse_iterator;
67
69 SharedPreHashedString{static_default_instance()}
70 {}
71
72 explicit SharedPreHashedString(std::string_view str)
73 {
74 const size_t num_bytes_allocated =
75 sizeof(Metadata) + // metadata
76 sizeof(value_type)*str.size() + // character data
77 sizeof(value_type); // NUL terminator
78
79 ptr_ = ::operator new(num_bytes_allocated, std::align_val_t{alignof(Metadata)});
80
81 // initialize `Metadata` at bytes `[0, sizeof(Metadata))`
82 static_assert(std::is_nothrow_constructible_v<Metadata, decltype(str)>);
83 new (ptr_) Metadata{str};
84
85 // initialize character data at `[sizeof(Metadata), sizeof(Metadata) + str.size())`
86 static_assert(alignof(Metadata) >= alignof(value_type));
87 static_assert(std::is_nothrow_copy_constructible_v<value_type>);
88 auto* character_data_ptr = std::launder(reinterpret_cast<value_type*>(static_cast<std::byte*>(ptr_) + sizeof(Metadata)));
89 std::uninitialized_copy_n(str.data(), str.size(), character_data_ptr);
90
91 // initialize NUL terminator at `sizeof(Metadata) + str.size()`
93 }
94 explicit SharedPreHashedString(const char* s) : SharedPreHashedString{std::string_view{s}} {}
95 explicit SharedPreHashedString(std::nullptr_t) = delete;
96
98 ptr_{src.ptr_}
99 {
100 static_cast<Metadata*>(ptr_)->num_owners.fetch_add(1, std::memory_order_relaxed);
101 }
102
104 ptr_{tmp.ptr_}
105 {
106 static_cast<Metadata*>(ptr_)->num_owners.fetch_add(1, std::memory_order_relaxed);
107 }
108
110 {
112 swap(*this, copy);
113 return *this;
114 }
115
117 {
118 swap(*this, tmp);
119 return *this;
120 }
121
123 {
124 if (static_cast<Metadata*>(ptr_)->num_owners.fetch_sub(1, std::memory_order_relaxed) == 1) {
125 ::operator delete(ptr_, std::align_val_t{alignof(Metadata)});
126 }
127 }
128
130 {
131 std::swap(a.ptr_, b.ptr_);
132 }
133
134 // returns a reference to the character at specified position `pos` if `pos <= size()`. Bounds checking
135 // is performed, and an exception of type `std::out_of_range` will be thrown on invalid access
137 {
138 return std::string_view{*this}.at(pos);
139 }
140
141 // returns a reference to the character at the specified position `pos` if `pos < size()`, or a
142 // reference to `value_type()` if `pos == size()`. No bounds checking is performed. If `pos > size()`
143 // the behavior is undefined
145 {
146 return data()[pos];
147 }
148
149 // returns a reference to the first character in the string. The behavior is undefined if `empty()` is `true`
151 {
152 return *data();
153 }
154
155 // returns a reference to the last character in the string. The behavior is undefined if `empty()` is `true`
157 {
158 return std::string_view{*this}.back();
159 }
160
161 // returns a pointer to the underlying array serving as character storage
162 const value_type* data() const
163 {
164 static_assert(sizeof(value_type) == 1);
165 return static_cast<value_type*>(ptr_) + sizeof(Metadata);
166 }
167
168 // returns a pointer to a null-terminated character array with data equivalent to those stored in the string.
169 const value_type* c_str() const
170 {
171 return data();
172 }
173
174 // returns a non-modifiable `std::string_view` of the string's data
175 operator std::string_view() const noexcept
176 {
177 return std::string_view{data(), size()};
178 }
179
180 // returns a non-modifiable `CStringView` of the string's data
182 {
183 return CStringView{data(), size()};
184 }
185
186 // returns an iterator to the first character in the string
188 {
189 return std::string_view{*this}.begin();
190 }
191
192 // returns a constant iterator to the first character in the string
194 {
195 return std::string_view{*this}.cbegin();
196 }
197
198 // returns an iterator to the character following the last character of the string
200 {
201 return std::string_view{*this}.end();
202 }
203
204 // returns a constant iterator to the character following the last character of the string
206 {
207 return std::string_view{*this}.cend();
208 }
209
210 // returns `true` if the string contains no characters
212 {
213 return size() == 0;
214 }
215
216 bool starts_with(std::string_view sv) const noexcept
217 {
218 return std::string_view{*this}.starts_with(sv);
219 }
220
221 // returns the number of characters in the string
223 {
224 return static_cast<const Metadata*>(ptr_)->size;
225 }
226
227 friend constexpr bool operator==(const SharedPreHashedString& lhs, const SharedPreHashedString& rhs)
228 {
229 return lhs.ptr_ == rhs.ptr_ or std::string_view{lhs} == std::string_view{rhs};
230 }
231
232 template<typename StringViewLike>
233 requires std::constructible_from<std::string_view, StringViewLike>
234 friend constexpr bool operator==(const SharedPreHashedString& lhs, const StringViewLike& rhs)
235 {
236 return std::string_view{lhs} == rhs;
237 }
238
239 template<typename StringViewLike>
240 requires std::constructible_from<std::string_view, StringViewLike>
241 friend constexpr bool operator==(const StringViewLike& lhs, const SharedPreHashedString& rhs)
242 {
243 return lhs == std::string_view{rhs};
244 }
245
246 friend constexpr auto operator<=>(const SharedPreHashedString& lhs, const SharedPreHashedString& rhs)
247 {
248 if (lhs.ptr_ == rhs.ptr_) {
249 return std::strong_ordering::equal;
250 }
251 return std::string_view{lhs} <=> std::string_view{rhs};
252 }
253
254 template<typename StringViewLike>
255 requires std::constructible_from<std::string_view, StringViewLike>
256 friend constexpr auto operator<=>(const SharedPreHashedString& lhs, const StringViewLike& rhs)
257 {
258 return std::string_view{lhs} <=> rhs;
259 }
260
261 template<typename StringViewLike>
262 requires std::constructible_from<std::string_view, StringViewLike>
263 friend constexpr auto operator<=>(const StringViewLike& lhs, const SharedPreHashedString& rhs)
264 {
265 return lhs <=> std::string_view{rhs};
266 }
267
268 friend std::ostream& operator<<(std::ostream& lhs, const SharedPreHashedString& rhs)
269 {
270 return lhs << std::string_view{rhs};
271 }
272
273 // returns the number of different `SharedPreHashedString` instances (`this` included)
274 // managing the same underlying string data. In a multithreaded environment, the value
275 // returned by `use_count` is approximate (i.e. the implementation uses a
276 // `std::memory_order_relaxed` operation to load the count)
278 {
279 return static_cast<long>(static_cast<Metadata*>(ptr_)->num_owners.load(std::memory_order_relaxed));
280 }
281 private:
282 friend struct std::hash<SharedPreHashedString>;
283
284 static const SharedPreHashedString& static_default_instance()
285 {
286 static SharedPreHashedString s_default_instance{std::string_view{}};
287 return s_default_instance;
288 }
289
290 // internal data structure that's placed at the front of the dynamically-allocated
291 // allocated memory block
292 struct Metadata final {
293
294 explicit Metadata(std::string_view str) noexcept :
295 size{str.size()},
296 hash{std::hash<std::string_view>{}(str)}
297 {}
298
299 size_t size;
300 size_t hash;
301 std::atomic<size_t> num_owners = 1;
302 };
303
304 // `StringNameData`, followed by characters, followed by NUL terminator
305 void* ptr_ = nullptr;
306 };
307}
308
309template<>
310struct std::hash<osc::SharedPreHashedString> final {
311 size_t operator()(const osc::SharedPreHashedString& str) const noexcept
312 {
313 return static_cast<const osc::SharedPreHashedString::Metadata*>(str.ptr_)->hash;
314 }
315};
Definition c_string_view.h:12
Definition shared_pre_hashed_string.h:55
const_reference at(size_type pos) const
Definition shared_pre_hashed_string.h:136
SharedPreHashedString(std::string_view str)
Definition shared_pre_hashed_string.h:72
friend constexpr auto operator<=>(const SharedPreHashedString &lhs, const StringViewLike &rhs)
Definition shared_pre_hashed_string.h:256
SharedPreHashedString(SharedPreHashedString &&tmp) noexcept
Definition shared_pre_hashed_string.h:103
SharedPreHashedString & operator=(const SharedPreHashedString &src) noexcept
Definition shared_pre_hashed_string.h:109
~SharedPreHashedString() noexcept
Definition shared_pre_hashed_string.h:122
std::string_view::const_reference const_reference
Definition shared_pre_hashed_string.h:62
friend constexpr auto operator<=>(const SharedPreHashedString &lhs, const SharedPreHashedString &rhs)
Definition shared_pre_hashed_string.h:246
SharedPreHashedString(const char *s)
Definition shared_pre_hashed_string.h:94
const_reference operator[](size_type pos) const
Definition shared_pre_hashed_string.h:144
friend constexpr bool operator==(const SharedPreHashedString &lhs, const StringViewLike &rhs)
Definition shared_pre_hashed_string.h:234
std::string_view::const_reverse_iterator const_reverse_iterator
Definition shared_pre_hashed_string.h:66
std::string_view::difference_type difference_type
Definition shared_pre_hashed_string.h:60
friend constexpr bool operator==(const StringViewLike &lhs, const SharedPreHashedString &rhs)
Definition shared_pre_hashed_string.h:241
long use_count() const noexcept
Definition shared_pre_hashed_string.h:277
SharedPreHashedString()
Definition shared_pre_hashed_string.h:68
SharedPreHashedString(const SharedPreHashedString &src) noexcept
Definition shared_pre_hashed_string.h:97
bool starts_with(std::string_view sv) const noexcept
Definition shared_pre_hashed_string.h:216
friend constexpr bool operator==(const SharedPreHashedString &lhs, const SharedPreHashedString &rhs)
Definition shared_pre_hashed_string.h:227
operator std::string_view() const noexcept
Definition shared_pre_hashed_string.h:175
std::string_view::value_type value_type
Definition shared_pre_hashed_string.h:58
size_type size() const
Definition shared_pre_hashed_string.h:222
const_iterator cend() const noexcept
Definition shared_pre_hashed_string.h:205
std::string_view::const_iterator iterator
Definition shared_pre_hashed_string.h:63
const_iterator end() const noexcept
Definition shared_pre_hashed_string.h:199
const_iterator begin() const noexcept
Definition shared_pre_hashed_string.h:187
const_reference front() const
Definition shared_pre_hashed_string.h:150
std::string_view::const_reverse_iterator reverse_iterator
Definition shared_pre_hashed_string.h:65
std::string_view::size_type size_type
Definition shared_pre_hashed_string.h:59
SharedPreHashedString(std::nullptr_t)=delete
SharedPreHashedString & operator=(SharedPreHashedString &&tmp) noexcept
Definition shared_pre_hashed_string.h:116
friend void swap(SharedPreHashedString &a, SharedPreHashedString &b) noexcept
Definition shared_pre_hashed_string.h:129
friend constexpr auto operator<=>(const StringViewLike &lhs, const SharedPreHashedString &rhs)
Definition shared_pre_hashed_string.h:263
const_reference back() const
Definition shared_pre_hashed_string.h:156
friend std::ostream & operator<<(std::ostream &lhs, const SharedPreHashedString &rhs)
Definition shared_pre_hashed_string.h:268
std::string_view::const_reference reference
Definition shared_pre_hashed_string.h:61
std::string_view::const_iterator const_iterator
Definition shared_pre_hashed_string.h:64
std::string_view::traits_type traits_type
Definition shared_pre_hashed_string.h:57
const value_type * c_str() const
Definition shared_pre_hashed_string.h:169
const value_type * data() const
Definition shared_pre_hashed_string.h:162
bool empty() const noexcept
Definition shared_pre_hashed_string.h:211
const_iterator cbegin() const noexcept
Definition shared_pre_hashed_string.h:193
size_t size(const OpenSim::Set< T, C > &s)
Definition open_sim_helpers.h:80
Definition custom_decoration_generator.h:5
constexpr U to(T &&value)
Definition conversion.h:56
size_t operator()(const osc::SharedPreHashedString &str) const noexcept
Definition shared_pre_hashed_string.h:311