dinit-util.h 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  1. #ifndef DINIT_UTIL_H_INCLUDED
  2. #define DINIT_UTIL_H_INCLUDED 1
  3. #include <string>
  4. #include <functional>
  5. #include <list>
  6. #include <vector>
  7. #include <algorithm>
  8. #include <stdexcept>
  9. #include <cstring>
  10. #include <cstddef>
  11. #include <cerrno>
  12. #include <sys/types.h>
  13. #include <unistd.h>
  14. #include <baseproc-sys.h>
  15. // Check if a value is one of several possible values.
  16. // Use like: value(x).is_in(1,2,3)
  17. template <typename T>
  18. class value_cls {
  19. const T &v;
  20. public:
  21. value_cls(const T &v) : v(v) {}
  22. template <typename U>
  23. bool is_in(U&& val)
  24. {
  25. return v == val;
  26. }
  27. template <typename U, typename ...V>
  28. bool is_in(U&&val, V&&... vals) {
  29. if (v == val)
  30. return true;
  31. return is_in(vals...);
  32. }
  33. };
  34. template <typename T>
  35. value_cls<T> value(const T &v)
  36. {
  37. return value_cls<T>(v);
  38. }
  39. // Since we are C++11, we don't have a std::string_view.
  40. class string_view
  41. {
  42. const char *s = nullptr;
  43. size_t count = 0;
  44. public:
  45. string_view() = default;
  46. string_view(const string_view &other) = default;
  47. string_view(const char *s_p, size_t count_p) noexcept : s(s_p), count(count_p) { }
  48. string_view(const char *s_p) noexcept : s(s_p), count(strlen(s_p)) { }
  49. string_view(const std::string &str) : s(str.data()), count(str.length()) { }
  50. string_view &operator=(const string_view &other) = default;
  51. bool operator==(const string_view &other) const noexcept
  52. {
  53. if (count != other.count) return false;
  54. return memcmp(s, other.s, count) == 0;
  55. }
  56. bool operator==(const char *other) const noexcept
  57. {
  58. if (strncmp(s, other, count) == 0) {
  59. if (other[count] == '\0') {
  60. return true;
  61. }
  62. }
  63. return false;
  64. }
  65. const char *data() const noexcept { return s; }
  66. size_t size() const noexcept { return count; }
  67. size_t length() const noexcept { return count; }
  68. bool empty() const noexcept { return count == 0; }
  69. string_view substr(size_t begin, size_t len) const noexcept
  70. {
  71. return string_view(s + begin, std::min(len, count - begin));
  72. }
  73. const char *begin() const noexcept { return s; }
  74. const char *end() const noexcept { return s + count; }
  75. std::reverse_iterator<const char *> rbegin() const noexcept { return std::reverse_iterator<const char *>(end()); }
  76. std::reverse_iterator<const char *> rend() const noexcept { return std::reverse_iterator<const char *>(begin()); }
  77. size_t find(char c) const noexcept {
  78. auto it = std::find(begin(), end(), c);
  79. if (it == end()) return std::string::npos;
  80. return (it - begin());
  81. }
  82. size_t rfind(char c) const noexcept {
  83. auto it = std::find(rbegin(), rend(), c);
  84. if (it == rend()) return std::string::npos;
  85. return (&(*it) - begin());
  86. }
  87. operator std::string() const
  88. {
  89. return std::string(s, count);
  90. }
  91. };
  92. // Complete read - read the specified size until end-of-file or error; continue read if
  93. // interrupted by signal.
  94. inline ssize_t complete_read(int fd, void * buf, size_t n)
  95. {
  96. char * cbuf = static_cast<char *>(buf);
  97. ssize_t r = 0;
  98. while ((size_t)r < n) {
  99. ssize_t res = bp_sys::read(fd, cbuf + r, n - r);
  100. if (res == 0) {
  101. return r;
  102. }
  103. if (res < 0) {
  104. if (errno == EINTR) {
  105. continue;
  106. }
  107. // If any other error, and we have successfully read some, return it:
  108. if (r == 0) {
  109. return -1;
  110. }
  111. else {
  112. return r;
  113. }
  114. }
  115. r += res;
  116. }
  117. return n;
  118. }
  119. // Combine two paths to produce a path. If the second path is absolute, it is returned unmodified;
  120. // otherwise, it is appended to the first path (with a slash separator added if needed).
  121. inline std::string combine_paths(string_view p1, const char * p2)
  122. {
  123. if (*p2 == 0) return (std::string)p1;
  124. if (p1.empty()) return std::string(p2);
  125. if (p2[0] == '/') return p2;
  126. if (*(p1.rbegin()) == '/') return (std::string)p1 + p2;
  127. return (std::string)p1 + '/' + p2;
  128. }
  129. // Find the parent path of a given path, which should refer to a named file or directory (not . or ..).
  130. // If the path contains no directory, returns the empty string.
  131. inline string_view parent_path(string_view p)
  132. {
  133. auto spos = p.rfind('/');
  134. if (spos == std::string::npos) {
  135. return string_view {};
  136. }
  137. return p.substr(0, spos + 1);
  138. }
  139. // Find the base name of a path (the name after the final '/').
  140. inline const char *base_name(const char *path) noexcept
  141. {
  142. const char * basen = path;
  143. const char * s = path;
  144. while (*s != 0) {
  145. if (*s == '/') basen = s + 1;
  146. s++;
  147. }
  148. return basen;
  149. }
  150. // Check if one string starts with another
  151. inline bool starts_with(const std::string &s, const char *prefix) noexcept
  152. {
  153. const char *sp = s.c_str();
  154. while (*sp != 0 && *prefix != 0) {
  155. if (*sp != *prefix) return false;
  156. sp++; prefix++;
  157. }
  158. return *prefix == 0;
  159. }
  160. // An allocator that doesn't value-initialise for construction. Eg for containers of primitive types this
  161. // allocator avoids the overhead of initialising new elements to 0.
  162. template <typename T>
  163. class default_init_allocator : public std::allocator<T>
  164. {
  165. using std::allocator<T>::allocator;
  166. public:
  167. template <typename U>
  168. struct rebind {
  169. using other = default_init_allocator<U>;
  170. };
  171. template <typename U = std::enable_if<std::is_default_constructible<T>::value>>
  172. void construct(T *obj)
  173. {
  174. // avoid value-initialisation:
  175. ::new(obj) T;
  176. }
  177. template <typename ...Args>
  178. void construct(T *obj, Args&&... args)
  179. {
  180. std::allocator<T>::construct(obj, std::forward<Args>(args)...);
  181. }
  182. };
  183. inline size_t hash(const string_view &str)
  184. {
  185. size_t end_pos = str.length();
  186. size_t hash_val = 0;
  187. for (size_t i = 0; i < end_pos; i += sizeof(size_t)) {
  188. // collect as many characters as will fit into size_t
  189. size_t hash_unit = 0;
  190. std::memcpy(&hash_unit, str.data() + i, std::min(sizeof(size_t), end_pos - i));
  191. // then incorporate the collected characters into the hash value
  192. hash_val *= 31;
  193. hash_val += hash_unit;
  194. }
  195. return hash_val;
  196. }
  197. struct hash_sv
  198. {
  199. size_t operator()(const string_view &str) const
  200. {
  201. return hash(str);
  202. }
  203. };
  204. inline bool operator==(string_view str1, const std::string &str2)
  205. {
  206. return str1 == string_view(str2);
  207. }
  208. inline bool operator==(const std::string &str2, string_view str1)
  209. {
  210. return str1 == string_view(str2);
  211. }
  212. // An equivalent to std::equal_to<void> (which is C++14)
  213. class dinit_equal_to
  214. {
  215. public:
  216. template <typename A, typename B>
  217. bool operator()(A &&a, B &&b)
  218. {
  219. return std::forward<A>(a) == std::forward<B>(b);
  220. }
  221. };
  222. // A set where we can check for membership via other-than-key-type values
  223. template <typename K, typename Hash = std::hash<K>, typename Equal = std::equal_to<K>>
  224. class dinit_unordered_set {
  225. using key_type = K;
  226. using value_type = K;
  227. using size_type = size_t;
  228. using hasher = Hash;
  229. using key_equal = Equal;
  230. hasher hash_f;
  231. key_equal key_equal_f;
  232. using bucket_vec = std::vector<std::list<K>>;
  233. bucket_vec buckets {};
  234. size_t current_size = 0;
  235. size_t current_limit = 0; // size limit before we need more buckets
  236. size_t buckets_to_max(size_t buckets) noexcept
  237. {
  238. // calculate "buckets * 3 / 4" but without risk of overflow at the multiply stage
  239. size_t base = buckets / 4 * 3;
  240. size_t extra = (buckets % 4) * 3 / 4;
  241. return base + extra;
  242. }
  243. void do_rehash(size_t new_buckets) noexcept
  244. {
  245. // note, buckets vector is already at least new_buckets in size
  246. size_t old_buckets = buckets.size();
  247. // first splice all nodes from all buckets into a single list
  248. std::list<K> all_nodes;
  249. for (size_t i = 0; i < old_buckets; ++i) {
  250. all_nodes.splice(all_nodes.end(), buckets[i]);
  251. }
  252. // now put all nodes into the correct bucket
  253. auto node_i = all_nodes.begin();
  254. while (node_i != all_nodes.end()) {
  255. auto next_node_i = std::next(node_i);
  256. size_t hashval = hash_f(*node_i);
  257. size_t bucket_num = hashval % new_buckets;
  258. buckets[bucket_num].splice(buckets[bucket_num].end(), all_nodes, node_i);
  259. node_i = next_node_i;
  260. }
  261. }
  262. public:
  263. class iterator
  264. {
  265. friend class dinit_unordered_set;
  266. public:
  267. using iterator_category = std::forward_iterator_tag;
  268. using difference_type = std::ptrdiff_t;
  269. using value_type = dinit_unordered_set::value_type;
  270. using pointer = value_type*;
  271. using reference = value_type&;
  272. protected:
  273. using list_iterator_t = typename std::list<K>::iterator;
  274. bucket_vec *buckets;
  275. list_iterator_t list_it;
  276. size_t bucket_num;
  277. public:
  278. iterator() noexcept : buckets(nullptr), list_it(), bucket_num(-1) { }
  279. iterator(bucket_vec *buckets_p, list_iterator_t lit, size_t bucket_p) noexcept
  280. : buckets(buckets_p), list_it(lit), bucket_num(bucket_p) { }
  281. iterator(const iterator &) noexcept = default;
  282. bool operator==(const iterator &other) noexcept
  283. {
  284. return other.bucket_num == bucket_num && other.list_it == list_it;
  285. }
  286. bool operator!=(const iterator &other) noexcept
  287. {
  288. return !(*this == other);
  289. }
  290. value_type &operator*() noexcept
  291. {
  292. return *list_it;
  293. }
  294. const value_type *operator->() noexcept
  295. {
  296. return &(*list_it);
  297. }
  298. iterator &operator++() noexcept
  299. {
  300. if (++list_it == (*buckets)[bucket_num].end()) {
  301. for (size_type i = bucket_num + 1; i < buckets->size(); ++i) {
  302. if (!(*buckets)[i].empty()) {
  303. list_it = (*buckets)[i].begin();
  304. bucket_num = i;
  305. return *this;
  306. }
  307. }
  308. list_it = {};
  309. bucket_num = -1;
  310. }
  311. return *this;
  312. }
  313. };
  314. class const_iterator : public iterator
  315. {
  316. public:
  317. using iterator::iterator;
  318. using pointer = const typename iterator::value_type*;
  319. using reference = const typename iterator::value_type&;
  320. const_iterator(const const_iterator &) noexcept = default;
  321. const_iterator(const iterator &other) noexcept : iterator(other) { }
  322. const value_type &operator*() noexcept
  323. {
  324. return *iterator::list_it;
  325. }
  326. const value_type *operator->() noexcept
  327. {
  328. return &(*iterator::list_it);
  329. }
  330. };
  331. private:
  332. // implement insert as a template so we can use it for rvalues and lvalues alike
  333. template <typename V>
  334. std::pair<iterator,bool> do_insert(V &&value)
  335. {
  336. size_t hashval = hash_f(value);
  337. size_t bucket_num;
  338. if (buckets.empty()) {
  339. buckets.resize(4); // good enough starting point
  340. current_limit = 3;
  341. bucket_num = hashval % buckets.size();
  342. }
  343. else {
  344. // First, check if the value is already present
  345. bucket_num = hashval % buckets.size();
  346. auto list_it = std::find_if(buckets[bucket_num].begin(), buckets[bucket_num].end(),
  347. [&](const key_type &k) { return key_equal_f(k,std::forward<V>(value)); });
  348. if (list_it != buckets[bucket_num].end()) {
  349. return { { &buckets, list_it, bucket_num }, false };
  350. }
  351. // Not present. Check if we need to expand.
  352. if (current_size >= current_limit) {
  353. if (buckets.size() <= (buckets.max_size() / 2)) {
  354. rehash(buckets.size() * 2);
  355. current_limit *= 2;
  356. bucket_num = hashval % buckets.size();
  357. }
  358. else {
  359. // in the unlikely event that we have hit the max_size, let's just become overloaded
  360. current_limit = -1;
  361. }
  362. }
  363. }
  364. auto list_it = buckets[bucket_num].insert(buckets[bucket_num].end(), std::forward<V>(value));
  365. ++current_size;
  366. return { { &buckets, list_it, bucket_num }, true };
  367. }
  368. public:
  369. dinit_unordered_set() noexcept = default;
  370. iterator end() noexcept
  371. {
  372. return iterator();
  373. }
  374. const_iterator end() const noexcept
  375. {
  376. return const_iterator();
  377. }
  378. iterator begin() noexcept
  379. {
  380. if (current_size == 0) return end();
  381. for (size_t i = 0; i < buckets.size(); ++i) {
  382. if (!buckets[i].empty()) {
  383. return { &buckets, buckets[i].begin(), i };
  384. }
  385. }
  386. return end(); // (should not be reachable)
  387. }
  388. const_iterator begin() const noexcept
  389. {
  390. auto *non_const_this = const_cast<dinit_unordered_set *>(this);
  391. return non_const_this->begin();
  392. }
  393. std::pair<iterator,bool> insert(const value_type &value)
  394. {
  395. return do_insert(value);
  396. }
  397. std::pair<iterator,bool> insert(value_type &&value)
  398. {
  399. return do_insert(std::move(value));
  400. }
  401. // Not available in C++: insert a different type of value; assumes that hash and equals
  402. // predicate can deal with the argument type, and that it can be converted to the actual
  403. // value.
  404. template <typename V>
  405. std::pair<iterator,bool> insert_byval(V &&value)
  406. {
  407. return do_insert(std::forward<V>(value));
  408. }
  409. void rehash(size_type new_bucket_count)
  410. {
  411. // calculate minimum bucket count, limited by maximum possible count.
  412. size_t max_buckets = buckets.max_size();
  413. size_t max_count = buckets_to_max(max_buckets);
  414. size_t min_buckets;
  415. if (current_size > max_count) {
  416. min_buckets = max_buckets;
  417. }
  418. else {
  419. size_t base = current_size / 3 * 4;
  420. size_t extra = (current_size % 3) * 4 / 3;
  421. min_buckets = base + extra;
  422. }
  423. new_bucket_count = std::max(new_bucket_count, min_buckets);
  424. if (new_bucket_count < buckets.size()) {
  425. // buckets vector will shrink: we need to reallocate entries into their new buckets first
  426. do_rehash(new_bucket_count);
  427. buckets.resize(new_bucket_count);
  428. }
  429. else {
  430. // grow the vector, then rehash
  431. buckets.resize(new_bucket_count);
  432. do_rehash(new_bucket_count);
  433. }
  434. }
  435. template <typename V>
  436. iterator find(const V &value) noexcept
  437. {
  438. if (buckets.empty()) return end();
  439. size_t hashval = hash_f(value);
  440. size_t bucket_num = hashval % buckets.size();
  441. for (auto list_it = buckets[bucket_num].begin(); list_it != buckets[bucket_num].end(); ++list_it) {
  442. if (key_equal_f(*list_it, value)) {
  443. return { &buckets, list_it, bucket_num };
  444. }
  445. }
  446. return end();
  447. }
  448. template <typename V>
  449. const_iterator find(const V &value) const noexcept
  450. {
  451. auto *non_const_this = const_cast<dinit_unordered_set *>(this);
  452. return non_const_this->find(value);
  453. }
  454. iterator erase(iterator pos) noexcept
  455. {
  456. auto new_it = std::next(pos);
  457. buckets[pos.bucket_num].erase(pos.list_it);
  458. return new_it;
  459. }
  460. template <typename V>
  461. size_type erase(const V &value) noexcept
  462. {
  463. auto it = find(value);
  464. if (it != end()) {
  465. buckets[it.bucket_num].erase(it.list_it);
  466. return 1;
  467. }
  468. return 0;
  469. }
  470. template <typename V>
  471. bool contains(const V &value) const noexcept
  472. {
  473. return find(value) != end();
  474. }
  475. size_type size() const noexcept
  476. {
  477. return current_size;
  478. }
  479. bool empty() const noexcept
  480. {
  481. return current_size == 0;
  482. }
  483. void clear() noexcept
  484. {
  485. for (auto &bucket : buckets) {
  486. bucket.clear();
  487. }
  488. current_size = 0;
  489. current_limit = 0;
  490. buckets.clear();
  491. try {
  492. buckets.shrink_to_fit();
  493. }
  494. catch (std::bad_alloc &) {
  495. // ignore any (unlikely) error
  496. }
  497. }
  498. };
  499. // a linked-list-set, i.e. a set that tracks insertion order
  500. template <typename K, typename Hash = std::hash<K>, typename Equal = std::equal_to<K>>
  501. class linked_uo_set
  502. {
  503. using key_type = K;
  504. using value_type = K;
  505. using size_type = size_t;
  506. using hasher = Hash;
  507. using key_equal = Equal;
  508. struct linked_record
  509. {
  510. K value;
  511. linked_record *next;
  512. };
  513. struct lr_hash
  514. {
  515. hasher lr_hash_f;
  516. template <typename T>
  517. size_t operator()(const T &hval)
  518. {
  519. return lr_hash_f(hval);
  520. }
  521. size_t operator()(const linked_record &r)
  522. {
  523. return lr_hash_f(r.value);
  524. }
  525. };
  526. struct lr_equ
  527. {
  528. key_equal lr_equal_f;
  529. template <typename T>
  530. bool operator()(const T &hval, const linked_record &rec)
  531. {
  532. return lr_equal_f(hval, rec.value);
  533. }
  534. template <typename T>
  535. bool operator()(const linked_record &rec, const T &hval)
  536. {
  537. return lr_equal_f(hval, rec.value);
  538. }
  539. bool operator()(const linked_record &rec1, const linked_record &rec2)
  540. {
  541. return lr_equal_f(rec1.value, rec2.value);
  542. }
  543. };
  544. dinit_unordered_set<linked_record, lr_hash, lr_equ> backing;
  545. linked_record *first = nullptr;
  546. linked_record *last = nullptr;
  547. public:
  548. // Add to the back of the linked set, if not already in the set.
  549. // Return true if added, false if was already in the set.
  550. bool add_back(const value_type &value)
  551. {
  552. auto it_b = backing.insert({value, nullptr});
  553. if (it_b.second) {
  554. auto *new_ent = &(*it_b.first);
  555. if (last != nullptr) {
  556. last->next = new_ent;
  557. }
  558. last = new_ent;
  559. if (first == nullptr) {
  560. first = new_ent;
  561. }
  562. return true;
  563. }
  564. return false;
  565. }
  566. class iterator
  567. {
  568. linked_record *current = nullptr;
  569. public:
  570. using iterator_category = std::forward_iterator_tag;
  571. using difference_type = std::ptrdiff_t;
  572. using value_type = linked_uo_set::value_type;
  573. using pointer = value_type*;
  574. using reference = value_type&;
  575. iterator(linked_record *record) : current(record) { }
  576. bool operator==(const iterator &other) noexcept
  577. {
  578. return other.current == current;
  579. }
  580. bool operator!=(const iterator &other) noexcept
  581. {
  582. return !(*this == other);
  583. }
  584. value_type &operator*() noexcept
  585. {
  586. return current->value;
  587. }
  588. const value_type *operator->() noexcept
  589. {
  590. return &(current->value);
  591. }
  592. iterator &operator++() noexcept
  593. {
  594. current = current->next;
  595. return *this;
  596. }
  597. };
  598. iterator begin()
  599. {
  600. return iterator(first);
  601. }
  602. iterator end()
  603. {
  604. return iterator(nullptr);
  605. }
  606. };
  607. // string that maintains a heap allocation always. Moving a ha_string does not invalidate pointers
  608. // to characters within the string.
  609. class ha_string {
  610. char *str_data = nullptr;
  611. size_t str_len = 0;
  612. public:
  613. ha_string()
  614. {
  615. }
  616. ha_string(const char *cstr, size_t clen)
  617. {
  618. str_data = new char[clen + 1];
  619. memcpy(str_data, cstr, clen + 1);
  620. str_len = clen;
  621. }
  622. ha_string(const char *cstr) : ha_string(cstr, strlen(cstr))
  623. {
  624. }
  625. ha_string(const ha_string &other)
  626. {
  627. operator=(other);
  628. }
  629. ha_string(const ha_string &&other)
  630. {
  631. operator=(std::move(other));
  632. }
  633. ha_string &operator=(const ha_string &other)
  634. {
  635. char *new_str_data = new char[other.str_len + 1];
  636. delete[] str_data;
  637. memcpy(new_str_data, other.str_data, other.str_len + 1);
  638. str_data = new_str_data;
  639. str_len = other.str_len;
  640. return *this;
  641. }
  642. ha_string &operator=(ha_string &&other) noexcept
  643. {
  644. delete[] str_data;
  645. str_data = other.str_data;
  646. str_len = other.str_len;
  647. other.str_data = nullptr;
  648. other.str_len = 0;
  649. return *this;
  650. }
  651. ha_string &operator=(const std::string &other)
  652. {
  653. char *new_str_data = new char[other.length() + 1];
  654. delete[] str_data;
  655. memcpy(new_str_data, other.data(), other.length() + 1);
  656. str_data = new_str_data;
  657. str_len = other.length();
  658. return *this;
  659. }
  660. ~ha_string()
  661. {
  662. delete[] str_data;
  663. }
  664. char &operator[](size_t index) noexcept
  665. {
  666. return str_data[index];
  667. }
  668. bool operator==(const char *other) noexcept
  669. {
  670. return strncmp(str_data, other, str_len) == 0;
  671. }
  672. char *c_str() noexcept
  673. {
  674. return str_data;
  675. }
  676. const char *c_str() const noexcept
  677. {
  678. return str_data;
  679. }
  680. bool empty() const noexcept
  681. {
  682. return str_len == 0;
  683. }
  684. size_t length() const noexcept
  685. {
  686. return str_len;
  687. }
  688. std::string substr(size_t pos, size_t count = (size_t)-1)
  689. {
  690. if (pos > str_len) throw std::out_of_range("pos exceeds string length");
  691. size_t sub_len = std::min(count, str_len - pos);
  692. return std::string(str_data + pos, sub_len);
  693. }
  694. };
  695. #endif