Seastar
High performance C++ framework for concurrent servers
sstring.hh
1 /*
2  * This file is open source software, licensed to you under the terms
3  * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
4  * distributed with this work for additional information regarding copyright
5  * ownership. You may not use this file except in compliance with the License.
6  *
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing,
12  * software distributed under the License is distributed on an
13  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14  * KIND, either express or implied. See the License for the
15  * specific language governing permissions and limitations
16  * under the License.
17  */
18 /*
19  * Copyright 2014 Cloudius Systems
20  */
21 
22 #pragma once
23 
24 #ifndef SEASTAR_MODULE
25 #include <stdint.h>
26 #include <algorithm>
27 #include <cassert>
28 #if __has_include(<compare>)
29 #include <compare>
30 #endif
31 #include <string>
32 #include <vector>
33 #include <unordered_map>
34 #include <cstring>
35 #include <stdexcept>
36 #include <initializer_list>
37 #include <istream>
38 #include <ostream>
39 #include <functional>
40 #include <type_traits>
41 #include <fmt/ostream.h>
42 #endif
43 #include <seastar/util/concepts.hh>
44 #include <seastar/util/std-compat.hh>
45 #include <seastar/util/modules.hh>
46 #include <seastar/core/temporary_buffer.hh>
47 
48 namespace seastar {
49 
50 SEASTAR_MODULE_EXPORT_BEGIN
51 
52 template <typename char_type, typename Size, Size max_size, bool NulTerminate = true>
53 class basic_sstring;
54 
55 #ifdef SEASTAR_SSTRING
56 // Older std::string used atomic reference counting and had no small-buffer-optimization.
57 // At some point the new std::string ABI improved -- no reference counting plus the small
58 // buffer optimization. However, aliasing seastar::sstring to std::string still ends up
59 // with a small performance degradation. (FIXME?)
60 using sstring = basic_sstring<char, uint32_t, 15>;
61 #else
62 using sstring = std::string;
63 #endif
64 
65 SEASTAR_MODULE_EXPORT_END
66 
67 namespace internal {
68 [[noreturn]] void throw_bad_alloc();
69 [[noreturn]] void throw_sstring_overflow();
70 [[noreturn]] void throw_sstring_out_of_range();
71 }
72 
73 SEASTAR_MODULE_EXPORT
74 template <typename char_type, typename Size, Size max_size, bool NulTerminate>
76  static_assert(
77  (std::is_same_v<char_type, char>
78  || std::is_same_v<char_type, signed char>
79  || std::is_same_v<char_type, unsigned char>),
80  "basic_sstring only supports single byte char types");
81  union contents {
82  struct external_type {
83  char_type* str;
84  Size size;
85  int8_t pad;
86  } external;
87  struct internal_type {
88  char_type str[max_size];
89  int8_t size;
90  } internal;
91  static_assert(sizeof(external_type) <= sizeof(internal_type), "max_size too small");
92  static_assert(max_size <= 127, "max_size too large");
93  } u;
94  bool is_internal() const noexcept {
95  return u.internal.size >= 0;
96  }
97  bool is_external() const noexcept {
98  return !is_internal();
99  }
100  const char_type* str() const noexcept {
101  return is_internal() ? u.internal.str : u.external.str;
102  }
103  char_type* str() noexcept {
104  return is_internal() ? u.internal.str : u.external.str;
105  }
106 
107 public:
108  using value_type = char_type;
109  using traits_type = std::char_traits<char_type>;
110  using allocator_type = std::allocator<char_type>;
111  using reference = char_type&;
112  using const_reference = const char_type&;
113  using pointer = char_type*;
114  using const_pointer = const char_type*;
115  using iterator = char_type*;
116  using const_iterator = const char_type*;
117  // FIXME: add reverse_iterator and friend
118  using difference_type = ssize_t; // std::make_signed_t<Size> can be too small
119  using size_type = Size;
120  static constexpr size_type npos = static_cast<size_type>(-1);
121  static constexpr unsigned padding() { return unsigned(NulTerminate); }
122 public:
123  struct initialized_later {};
124 
125  basic_sstring() noexcept {
126  u.internal.size = 0;
127  if (NulTerminate) {
128  u.internal.str[0] = '\0';
129  }
130  }
131  basic_sstring(const basic_sstring& x) {
132  if (x.is_internal()) {
133  u.internal = x.u.internal;
134  } else {
135  u.internal.size = -1;
136  u.external.str = reinterpret_cast<char_type*>(std::malloc(x.u.external.size + padding()));
137  if (!u.external.str) {
138  internal::throw_bad_alloc();
139  }
140  std::copy(x.u.external.str, x.u.external.str + x.u.external.size + padding(), u.external.str);
141  u.external.size = x.u.external.size;
142  }
143  }
144  basic_sstring(basic_sstring&& x) noexcept {
145 #pragma GCC diagnostic push
146  // Is a small-string construction is followed by this move constructor, then the trailing bytes
147  // of x.u are not initialized, but copied. gcc complains, but it is both legitimate to copy
148  // these bytes, and more efficient than a variable-size copy
149 #pragma GCC diagnostic ignored "-Wuninitialized"
150  u = x.u;
151 #pragma GCC diagnostic pop
152  x.u.internal.size = 0;
153  x.u.internal.str[0] = '\0';
154  }
155  basic_sstring(initialized_later, size_t size) {
156  if (size_type(size) != size) {
157  internal::throw_sstring_overflow();
158  }
159  if (size + padding() <= sizeof(u.internal.str)) {
160  if (NulTerminate) {
161  u.internal.str[size] = '\0';
162  }
163  u.internal.size = size;
164  } else {
165  u.internal.size = -1;
166  u.external.str = reinterpret_cast<char_type*>(std::malloc(size + padding()));
167  if (!u.external.str) {
168  internal::throw_bad_alloc();
169  }
170  u.external.size = size;
171  if (NulTerminate) {
172  u.external.str[size] = '\0';
173  }
174  }
175  }
176  basic_sstring(const char_type* x, size_t size) {
177  if (size_type(size) != size) {
178  internal::throw_sstring_overflow();
179  }
180  if (size + padding() <= sizeof(u.internal.str)) {
181  std::copy(x, x + size, u.internal.str);
182  if (NulTerminate) {
183  u.internal.str[size] = '\0';
184  }
185  u.internal.size = size;
186  } else {
187  u.internal.size = -1;
188  u.external.str = reinterpret_cast<char_type*>(std::malloc(size + padding()));
189  if (!u.external.str) {
190  internal::throw_bad_alloc();
191  }
192  u.external.size = size;
193  std::copy(x, x + size, u.external.str);
194  if (NulTerminate) {
195  u.external.str[size] = '\0';
196  }
197  }
198  }
199 
200  basic_sstring(size_t size, char_type x) : basic_sstring(initialized_later(), size) {
201  memset(begin(), x, size);
202  }
203 
204  basic_sstring(const char* x) : basic_sstring(reinterpret_cast<const char_type*>(x), std::strlen(x)) {}
205  basic_sstring(std::basic_string<char_type>& x) : basic_sstring(x.c_str(), x.size()) {}
206  basic_sstring(std::initializer_list<char_type> x) : basic_sstring(x.begin(), x.end() - x.begin()) {}
207  basic_sstring(const char_type* b, const char_type* e) : basic_sstring(b, e - b) {}
208  basic_sstring(const std::basic_string<char_type>& s)
209  : basic_sstring(s.data(), s.size()) {}
210  template <typename InputIterator>
211  basic_sstring(InputIterator first, InputIterator last)
212  : basic_sstring(initialized_later(), std::distance(first, last)) {
213  std::copy(first, last, begin());
214  }
215  explicit basic_sstring(std::basic_string_view<char_type, traits_type> v)
216  : basic_sstring(v.data(), v.size()) {
217  }
218  ~basic_sstring() noexcept {
219  if (is_external()) {
220  std::free(u.external.str);
221  }
222  }
223  basic_sstring& operator=(const basic_sstring& x) {
224  basic_sstring tmp(x);
225  swap(tmp);
226  return *this;
227  }
228  basic_sstring& operator=(basic_sstring&& x) noexcept {
229  if (this != &x) {
230  this->~basic_sstring();
231  new (this) basic_sstring(std::move(x));
232  }
233  return *this;
234  }
235  operator std::basic_string<char_type>() const {
236  return { str(), size() };
237  }
238 
239  size_t size() const noexcept {
240  return is_internal() ? u.internal.size : u.external.size;
241  }
242 
243  size_t length() const noexcept {
244  return size();
245  }
246 
247  size_t find(char_type t, size_t pos = 0) const noexcept {
248  const char_type* it = str() + pos;
249  const char_type* end = str() + size();
250  while (it < end) {
251  if (*it == t) {
252  return it - str();
253  }
254  it++;
255  }
256  return npos;
257  }
258 
259  size_t find(const char_type* c_str, size_t pos, size_t len2) const noexcept {
260  assert(c_str != nullptr || len2 == 0);
261  if (pos > size()) {
262  return npos;
263  }
264 
265  if (len2 == 0) {
266  return pos;
267  }
268 
269  const char_type* it = str() + pos;
270  const char_type* end = str() + size();
271  size_t len1 = end - it;
272  if (len1 < len2) {
273  return npos;
274  }
275 
276  char_type f2 = *c_str;
277  while (true) {
278  len1 = end - it;
279  if (len1 < len2) {
280  return npos;
281  }
282 
283  // find the first byte of pattern string matching in source string
284  it = traits_type::find(it, len1 - len2 + 1, f2);
285  if (it == nullptr) {
286  return npos;
287  }
288 
289  if (traits_type::compare(it, c_str, len2) == 0) {
290  return it - str();
291  }
292 
293  ++it;
294  }
295  }
296 
297  constexpr size_t find(const char_type* s, size_t pos = 0) const noexcept {
298  return find(s, pos, traits_type::length(s));
299  }
300 
301  size_t find(const basic_sstring& s, size_t pos = 0) const noexcept {
302  return find(s.str(), pos, s.size());
303  }
304 
305  template<class StringViewLike,
306  std::enable_if_t<std::is_convertible_v<StringViewLike,
307  std::basic_string_view<char_type, traits_type>>,
308  int> = 0>
309  size_t find(const StringViewLike& sv_like, size_type pos = 0) const noexcept {
310  std::basic_string_view<char_type, traits_type> sv = sv_like;
311  return find(sv.data(), pos, sv.size());
312  }
313 
320  size_t find_last_of (char_type c, size_t pos = npos) const noexcept {
321  const char_type* str_start = str();
322  if (size()) {
323  if (pos >= size()) {
324  pos = size() - 1;
325  }
326  const char_type* p = str_start + pos + 1;
327  do {
328  p--;
329  if (*p == c) {
330  return (p - str_start);
331  }
332  } while (p != str_start);
333  }
334  return npos;
335  }
336 
343  basic_sstring& append (const char_type* s, size_t n) {
344  basic_sstring ret(initialized_later(), size() + n);
345  std::copy(begin(), end(), ret.begin());
346  std::copy(s, s + n, ret.begin() + size());
347  *this = std::move(ret);
348  return *this;
349  }
350 
356  template <class Operation>
357  SEASTAR_CONCEPT( requires std::is_invocable_r_v<size_t, Operation, char_type*, size_t> )
358  void resize_and_overwrite(size_t n, Operation op) {
359  if (n > size()) {
360  *this = basic_sstring(initialized_later(), n);
361  }
362  size_t r = std::move(op)(data(), n);
363  assert(r <= n);
364  resize(r);
365  }
366 
372  void resize(size_t n, const char_type c = '\0') {
373  if (n > size()) {
374  *this += basic_sstring(n - size(), c);
375  } else if (n < size()) {
376  if (is_internal()) {
377  u.internal.size = n;
378  if (NulTerminate) {
379  u.internal.str[n] = '\0';
380  }
381  } else if (n + padding() <= sizeof(u.internal.str)) {
382  *this = basic_sstring(u.external.str, n);
383  } else {
384  u.external.size = n;
385  if (NulTerminate) {
386  u.external.str[n] = '\0';
387  }
388  }
389  }
390  }
391 
396  basic_sstring& replace(size_type pos, size_type n1, const char_type* s,
397  size_type n2) {
398  if (pos > size()) {
399  internal::throw_sstring_out_of_range();
400  }
401 
402  if (n1 > size() - pos) {
403  n1 = size() - pos;
404  }
405 
406  if (n1 == n2) {
407  if (n2) {
408  std::copy(s, s + n2, begin() + pos);
409  }
410  return *this;
411  }
412  basic_sstring ret(initialized_later(), size() + n2 - n1);
413  char_type* p= ret.begin();
414  std::copy(begin(), begin() + pos, p);
415  p += pos;
416  if (n2) {
417  std::copy(s, s + n2, p);
418  }
419  p += n2;
420  std::copy(begin() + pos + n1, end(), p);
421  *this = std::move(ret);
422  return *this;
423  }
424 
425  template <class InputIterator>
426  basic_sstring& replace (const_iterator i1, const_iterator i2,
427  InputIterator first, InputIterator last) {
428  if (i1 < begin() || i1 > end() || i2 < begin()) {
429  internal::throw_sstring_out_of_range();
430  }
431  if (i2 > end()) {
432  i2 = end();
433  }
434 
435  if (i2 - i1 == last - first) {
436  //in place replacement
437  std::copy(first, last, const_cast<char_type*>(i1));
438  return *this;
439  }
440  basic_sstring ret(initialized_later(), size() + (last - first) - (i2 - i1));
441  char_type* p = ret.begin();
442  p = std::copy(cbegin(), i1, p);
443  p = std::copy(first, last, p);
444  std::copy(i2, cend(), p);
445  *this = std::move(ret);
446  return *this;
447  }
448 
449  iterator erase(iterator first, iterator last) {
450  size_t pos = first - begin();
451  replace(pos, last - first, nullptr, 0);
452  return begin() + pos;
453  }
454 
459  template <class InputIterator>
460  void insert(const_iterator p, InputIterator beg, InputIterator end) {
461  replace(p, p, beg, end);
462  }
463 
464 
470  reference
471  front() noexcept {
472  assert(!empty());
473  return *str();
474  }
475 
481  const_reference
482  front() const noexcept {
483  assert(!empty());
484  return *str();
485  }
486 
492  reference
493  back() noexcept {
494  return operator[](size() - 1);
495  }
496 
502  const_reference
503  back() const noexcept {
504  return operator[](size() - 1);
505  }
506 
507  basic_sstring substr(size_t from, size_t len = npos) const {
508  if (from > size()) {
509  internal::throw_sstring_out_of_range();
510  }
511  if (len > size() - from) {
512  len = size() - from;
513  }
514  if (len == 0) {
515  return "";
516  }
517  return { str() + from , len };
518  }
519 
520  const char_type& at(size_t pos) const {
521  if (pos >= size()) {
522  internal::throw_sstring_out_of_range();
523  }
524  return *(str() + pos);
525  }
526 
527  char_type& at(size_t pos) {
528  if (pos >= size()) {
529  internal::throw_sstring_out_of_range();
530  }
531  return *(str() + pos);
532  }
533 
534  bool empty() const noexcept {
535  return u.internal.size == 0;
536  }
537 
538  // Deprecated March 2020.
539  [[deprecated("Use = {}")]]
540  void reset() noexcept {
541  if (is_external()) {
542  std::free(u.external.str);
543  }
544  u.internal.size = 0;
545  if (NulTerminate) {
546  u.internal.str[0] = '\0';
547  }
548  }
549  temporary_buffer<char_type> release() && {
550  if (is_external()) {
551  auto ptr = u.external.str;
552  auto size = u.external.size;
553  u.external.str = nullptr;
554  u.external.size = 0;
555  return temporary_buffer<char_type>(ptr, size, make_free_deleter(ptr));
556  } else {
557  auto buf = temporary_buffer<char_type>(u.internal.size);
558  std::copy(u.internal.str, u.internal.str + u.internal.size, buf.get_write());
559  u.internal.size = 0;
560  if (NulTerminate) {
561  u.internal.str[0] = '\0';
562  }
563  return buf;
564  }
565  }
566  int compare(std::basic_string_view<char_type, traits_type> x) const noexcept {
567  auto n = traits_type::compare(begin(), x.begin(), std::min(size(), x.size()));
568  if (n != 0) {
569  return n;
570  }
571  if (size() < x.size()) {
572  return -1;
573  } else if (size() > x.size()) {
574  return 1;
575  } else {
576  return 0;
577  }
578  }
579 
580  int compare(size_t pos, size_t sz, std::basic_string_view<char_type, traits_type> x) const {
581  if (pos > size()) {
582  internal::throw_sstring_out_of_range();
583  }
584 
585  sz = std::min(size() - pos, sz);
586  auto n = traits_type::compare(begin() + pos, x.begin(), std::min(sz, x.size()));
587  if (n != 0) {
588  return n;
589  }
590  if (sz < x.size()) {
591  return -1;
592  } else if (sz > x.size()) {
593  return 1;
594  } else {
595  return 0;
596  }
597  }
598 
599  constexpr bool starts_with(std::basic_string_view<char_type, traits_type> sv) const noexcept {
600  return size() > sv.size() && compare(0, sv.size(), sv) == 0;
601  }
602 
603  constexpr bool starts_with(char_type c) const noexcept {
604  return !empty() && traits_type::eq(front(), c);
605  }
606 
607  constexpr bool starts_with(const char_type* s) const noexcept {
608  return starts_with(std::basic_string_view<char_type, traits_type>(s));
609  }
610 
611  constexpr bool ends_with(std::basic_string_view<char_type, traits_type> sv) const noexcept {
612  return size() > sv.size() && compare(size() - sv.size(), npos, sv) == 0;
613  }
614 
615  constexpr bool ends_with(char_type c) const noexcept {
616  return !empty() && traits_type::eq(back(), c);
617  }
618 
619  constexpr bool ends_with(const char_type* s) const noexcept {
620  return ends_with(std::basic_string_view<char_type, traits_type>(s));
621  }
622 
623  constexpr bool contains(std::basic_string_view<char_type, traits_type> sv) const noexcept {
624  return find(sv) != npos;
625  }
626 
627  constexpr bool contains(char_type c) const noexcept {
628  return find(c) != npos;
629  }
630 
631  constexpr bool contains(const char_type* s) const noexcept {
632  return find(s) != npos;
633  }
634 
635  void swap(basic_sstring& x) noexcept {
636  contents tmp;
637  tmp = x.u;
638  x.u = u;
639  u = tmp;
640  }
641  char_type* data() noexcept {
642  return str();
643  }
644  const char_type* data() const noexcept {
645  return str();
646  }
647  const char_type* c_str() const noexcept {
648  return str();
649  }
650  const char_type* begin() const noexcept { return str(); }
651  const char_type* end() const noexcept { return str() + size(); }
652  const char_type* cbegin() const noexcept { return str(); }
653  const char_type* cend() const noexcept { return str() + size(); }
654  char_type* begin() noexcept { return str(); }
655  char_type* end() noexcept { return str() + size(); }
656  bool operator==(const basic_sstring& x) const noexcept {
657  return size() == x.size() && std::equal(begin(), end(), x.begin());
658  }
659  bool operator!=(const basic_sstring& x) const noexcept {
660  return !operator==(x);
661  }
662  constexpr bool operator==(const std::basic_string<char_type> x) const noexcept {
663  return compare(x) == 0;
664  }
665  constexpr bool operator==(const char_type* x) const noexcept {
666  return compare(x) == 0;
667  }
668 #if __cpp_lib_three_way_comparison
669  constexpr std::strong_ordering operator<=>(const auto& x) const noexcept {
670  return compare(x) <=> 0;
671  }
672 #else
673  bool operator<(const basic_sstring& x) const noexcept {
674  return compare(x) < 0;
675  }
676 #endif
677  basic_sstring operator+(const basic_sstring& x) const {
678  basic_sstring ret(initialized_later(), size() + x.size());
679  std::copy(begin(), end(), ret.begin());
680  std::copy(x.begin(), x.end(), ret.begin() + size());
681  return ret;
682  }
683  basic_sstring& operator+=(const basic_sstring& x) {
684  return *this = *this + x;
685  }
686  char_type& operator[](size_type pos) noexcept {
687  return str()[pos];
688  }
689  const char_type& operator[](size_type pos) const noexcept {
690  return str()[pos];
691  }
692 
693  operator std::basic_string_view<char_type>() const noexcept {
694  // we assume that std::basic_string_view<char_type>(str(), size())
695  // won't throw, although it is not specified as noexcept in
696  // https://en.cppreference.com/w/cpp/string/basic_string_view/basic_string_view
697  // at this time (C++20).
698  //
699  // This is similar to std::string operator std::basic_string_view:
700  // https://en.cppreference.com/w/cpp/string/basic_string/operator_basic_string_view
701  // that is specified as noexcept too.
702  static_assert(noexcept(std::basic_string_view<char_type>(str(), size())));
703  return std::basic_string_view<char_type>(str(), size());
704  }
705 };
706 
707 namespace internal {
708 template <class T> struct is_sstring : std::false_type {};
709 template <typename char_type, typename Size, Size max_size, bool NulTerminate>
710 struct is_sstring<basic_sstring<char_type, Size, max_size, NulTerminate>> : std::true_type {};
711 }
712 
713 SEASTAR_MODULE_EXPORT
714 template <typename string_type = sstring>
715 string_type uninitialized_string(size_t size) {
716  if constexpr (internal::is_sstring<string_type>::value) {
717  return string_type(typename string_type::initialized_later(), size);
718  } else {
719  string_type ret;
720 #ifdef __cpp_lib_string_resize_and_overwrite
721  ret.resize_and_overwrite(size, [](string_type::value_type*, string_type::size_type n) { return n; });
722 #else
723  ret.resize(size);
724 #endif
725  return ret;
726  }
727 }
728 
729 SEASTAR_MODULE_EXPORT
730 template <typename char_type, typename size_type, size_type Max, size_type N, bool NulTerminate>
731 inline
732 basic_sstring<char_type, size_type, Max, NulTerminate>
733 operator+(const char(&s)[N], const basic_sstring<char_type, size_type, Max, NulTerminate>& t) {
734  using sstring = basic_sstring<char_type, size_type, Max, NulTerminate>;
735  // don't copy the terminating NUL character
736  sstring ret(typename sstring::initialized_later(), N-1 + t.size());
737  auto p = std::copy(std::begin(s), std::end(s)-1, ret.begin());
738  std::copy(t.begin(), t.end(), p);
739  return ret;
740 }
741 
742 template <typename T>
743 static inline
744 size_t constexpr str_len(const T& s) {
745  return std::string_view(s).size();
746 }
747 
748 SEASTAR_MODULE_EXPORT
749 template <typename char_type, typename size_type, size_type max_size>
750 inline
751 void swap(basic_sstring<char_type, size_type, max_size>& x,
752  basic_sstring<char_type, size_type, max_size>& y) noexcept
753 {
754  return x.swap(y);
755 }
756 
757 SEASTAR_MODULE_EXPORT
758 template <typename char_type, typename size_type, size_type max_size, bool NulTerminate, typename char_traits>
759 inline
760 std::basic_ostream<char_type, char_traits>&
761 operator<<(std::basic_ostream<char_type, char_traits>& os,
762  const basic_sstring<char_type, size_type, max_size, NulTerminate>& s) {
763  return os.write(s.begin(), s.size());
764 }
765 
766 SEASTAR_MODULE_EXPORT
767 template <typename char_type, typename size_type, size_type max_size, bool NulTerminate, typename char_traits>
768 inline
769 std::basic_istream<char_type, char_traits>&
770 operator>>(std::basic_istream<char_type, char_traits>& is,
771  basic_sstring<char_type, size_type, max_size, NulTerminate>& s) {
772  std::string tmp;
773  is >> tmp;
774  s = tmp;
775  return is;
776 }
777 
778 }
779 
780 namespace std {
781 
782 SEASTAR_MODULE_EXPORT
783 template <typename char_type, typename size_type, size_type max_size, bool NulTerminate>
784 struct hash<seastar::basic_sstring<char_type, size_type, max_size, NulTerminate>> {
786  return std::hash<std::basic_string_view<char_type>>()(s);
787  }
788 };
789 
790 }
791 
792 namespace seastar {
793 
794 template <typename T>
795 static inline
796 void copy_str_to(char*& dst, const T& s) {
797  std::string_view v(s);
798  dst = std::copy(v.begin(), v.end(), dst);
799 }
800 
801 template <typename String = sstring, typename... Args>
802 static String make_sstring(Args&&... args)
803 {
804  String ret = uninitialized_string<String>((str_len(args) + ...));
805  auto dst = ret.data();
806  (copy_str_to(dst, args), ...);
807  return ret;
808 }
809 
810 namespace internal {
811 template <typename string_type, typename T>
812 string_type to_sstring(T value) {
813  auto size = fmt::formatted_size("{}", value);
814  auto formatted = uninitialized_string<string_type>(size);
815  fmt::format_to(formatted.data(), "{}", value);
816  return formatted;
817 }
818 
819 template <typename string_type>
820 string_type to_sstring(const char* value) {
821  return string_type(value);
822 }
823 
824 template <typename string_type>
825 string_type to_sstring(sstring value) {
826  return value;
827 }
828 
829 template <typename string_type>
830 string_type to_sstring(const temporary_buffer<char>& buf) {
831  return string_type(buf.get(), buf.size());
832 }
833 }
834 
835 template <typename string_type = sstring, typename T>
836 string_type to_sstring(T value) {
837  return internal::to_sstring<string_type>(value);
838 }
839 }
840 
841 namespace std {
842 
843 SEASTAR_MODULE_EXPORT
844 template <typename T>
845 inline
846 std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
847  bool first = true;
848  os << "{";
849  for (auto&& elem : v) {
850  if (!first) {
851  os << ", ";
852  } else {
853  first = false;
854  }
855  os << elem;
856  }
857  os << "}";
858  return os;
859 }
860 
861 SEASTAR_MODULE_EXPORT
862 template <typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
863 std::ostream& operator<<(std::ostream& os, const std::unordered_map<Key, T, Hash, KeyEqual, Allocator>& v) {
864  bool first = true;
865  os << "{";
866  for (auto&& elem : v) {
867  if (!first) {
868  os << ", ";
869  } else {
870  first = false;
871  }
872  os << "{" << elem.first << " -> " << elem.second << "}";
873  }
874  os << "}";
875  return os;
876 }
877 }
878 
879 #if FMT_VERSION >= 90000
880 
881 // Due to https://github.com/llvm/llvm-project/issues/68849, we inherit
882 // from formatter<string_view> publicly rather than privately
883 
884 SEASTAR_MODULE_EXPORT
885 template <typename char_type, typename Size, Size max_size, bool NulTerminate>
886 struct fmt::formatter<seastar::basic_sstring<char_type, Size, max_size, NulTerminate>>
887  : public fmt::formatter<std::basic_string_view<char_type>> {
888  using format_as_t = std::basic_string_view<char_type>;
889  using base = fmt::formatter<format_as_t>;
890  template <typename FormatContext>
891  auto format(const ::seastar::basic_sstring<char_type, Size, max_size, NulTerminate>& s, FormatContext& ctx) const {
892  return base::format(format_as_t{s}, ctx);
893  }
894 };
895 
896 #endif
Definition: sstring.hh:75
reference back() noexcept
Definition: sstring.hh:493
const_reference back() const noexcept
Definition: sstring.hh:503
const_reference front() const noexcept
Definition: sstring.hh:482
void resize_and_overwrite(size_t n, Operation op)
Definition: sstring.hh:358
void insert(const_iterator p, InputIterator beg, InputIterator end)
Definition: sstring.hh:460
void resize(size_t n, const char_type c='\0')
Definition: sstring.hh:372
reference front() noexcept
Definition: sstring.hh:471
size_t find_last_of(char_type c, size_t pos=npos) const noexcept
Definition: sstring.hh:320
basic_sstring & replace(size_type pos, size_type n1, const char_type *s, size_type n2)
Definition: sstring.hh:396
basic_sstring & append(const char_type *s, size_t n)
Definition: sstring.hh:343
Seastar API namespace.
Definition: abort_on_ebadf.hh:26
sstring format(const char *fmt, A &&... a)
Definition: print.hh:142