libpqxx
The C++ client library for PostgreSQL
range.hxx
Go to the documentation of this file.
1 #ifndef PQXX_RANGE_HXX
2 #define PQXX_RANGE_HXX
3 
4 #if !defined(PQXX_HEADER_PRE)
5 # error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
6 #endif
7 
8 #include <format>
9 #include <utility>
10 #include <variant>
11 
13 
14 namespace pqxx
15 {
17 
23 struct no_bound final
24 {
25  template<typename TYPE>
26  [[nodiscard]] constexpr bool extends_down_to(TYPE const &) const noexcept
27  {
28  return true;
29  }
30  template<typename TYPE>
31  [[nodiscard]] constexpr bool extends_up_to(TYPE const &) const noexcept
32  {
33  return true;
34  }
35 };
36 
37 
39 template<typename T>
40 concept has_less = std::copy_constructible<T> and requires(T n) { n < n; };
41 
42 
44 template<typename T>
45 concept has_equal = requires(T n) { n == n; };
46 
47 
49 
52 template<has_less TYPE> class inclusive_bound final
53 {
54  // (Putting private section first to work around bug in gcc < 10: see #665.)
55 private:
56  TYPE m_value;
57 
58 public:
59  inclusive_bound() = delete;
60  constexpr explicit inclusive_bound(
61  TYPE const &value, sl loc = sl::current()) :
62  m_value{value}
63  {
64  if (is_null(value))
65  throw argument_error{"Got null value as an inclusive range bound.", loc};
66  }
67 
68  [[nodiscard]] constexpr TYPE const &get() const & noexcept
69  {
70  return m_value;
71  }
72 
74  [[nodiscard]] constexpr bool extends_down_to(TYPE const &value) const
75  noexcept(noexcept(value < m_value))
76  {
77  return not(value < m_value);
78  }
79 
81  [[nodiscard]] constexpr bool extends_up_to(TYPE const &value) const
82  noexcept(noexcept(value < m_value))
83  {
84  return not(m_value < value);
85  }
86 };
87 
88 
90 
93 template<has_less TYPE> class exclusive_bound final
94 {
95  // (Putting private section first to work around bug in gcc < 10: see #665.)
96 private:
97  TYPE m_value;
98 
99 public:
100  exclusive_bound() = delete;
101  constexpr explicit exclusive_bound(
102  TYPE const &value, sl loc = sl::current()) :
103  m_value{value}
104  {
105  if (is_null(value))
106  throw argument_error{"Got null value as an exclusive range bound.", loc};
107  }
108 
109  [[nodiscard]] constexpr TYPE const &get() const & noexcept
110  {
111  return m_value;
112  }
113 
115  [[nodiscard]] constexpr bool extends_down_to(TYPE const &value) const
116  noexcept(noexcept(m_value < value))
117  {
118  return m_value < value;
119  }
120 
122  [[nodiscard]] constexpr bool extends_up_to(TYPE const &value) const
123  noexcept(noexcept(value < m_value))
124  {
125  return value < m_value;
126  }
127 };
128 
129 
131 
134 template<has_less TYPE> class range_bound final
135 {
136  // (Putting private section first to work around bug in gcc < 10: see #665.)
137 private:
138  std::variant<no_bound, inclusive_bound<TYPE>, exclusive_bound<TYPE>> m_bound;
139 
140  static constexpr bool equal(TYPE const &lhs, TYPE const &rhs)
141  {
142  if constexpr (requires { lhs == rhs; })
143  // TYPE supports equality comparison.
144  return lhs == rhs;
145  else
146  // Oh well. We do require less-than comparison, so use that.
147  return not((lhs < rhs) or (rhs < lhs));
148  }
149 
150 public:
151  range_bound() = delete;
152 
153  // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
154  constexpr range_bound(no_bound) noexcept : m_bound{} {}
155 
156  // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
157  constexpr range_bound(inclusive_bound<TYPE> const &bound) noexcept(
158  noexcept(inclusive_bound<TYPE>{bound})) :
159  m_bound{bound}
160  {}
161 
162  // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
163  constexpr range_bound(exclusive_bound<TYPE> const &bound) noexcept(
164  noexcept(exclusive_bound{bound})) :
165  m_bound{bound}
166  {}
167 
168  constexpr range_bound(range_bound const &) noexcept(
169  noexcept(inclusive_bound<TYPE>{
170  std::declval<inclusive_bound<TYPE> const &>()}) and
171  noexcept(exclusive_bound<TYPE>{
172  std::declval<exclusive_bound<TYPE> const &>()})) = default;
173 
174  constexpr range_bound(range_bound &&) = default;
175 
176  ~range_bound() = default;
177 
178  constexpr bool operator==(range_bound const &rhs) const
179  {
180  if (this->is_limited())
181  return (
182  rhs.is_limited() and (this->is_inclusive() == rhs.is_inclusive()) and
183  equal(*this->value(), *rhs.value()));
184  else
185  return not rhs.is_limited();
186  }
187 
188  constexpr bool operator!=(range_bound const &rhs) const
189  noexcept(noexcept(*this == rhs))
190  {
191  return not(*this == rhs);
192  }
193  range_bound &operator=(range_bound const &) = default;
195 
197  [[nodiscard]] constexpr bool is_limited() const noexcept
198  {
199  return not std::holds_alternative<no_bound>(m_bound);
200  }
201 
203  [[nodiscard]] constexpr bool is_inclusive() const noexcept
204  {
205  return std::holds_alternative<inclusive_bound<TYPE>>(m_bound);
206  }
207 
209  [[nodiscard]] constexpr bool is_exclusive() const noexcept
210  {
211  return std::holds_alternative<exclusive_bound<TYPE>>(m_bound);
212  }
213 
215  [[nodiscard]] constexpr bool extends_down_to(TYPE const &value) const
216  {
217  return std::visit(
218  [&value](auto const &bound) noexcept(noexcept(bound.extends_down_to(
219  value))) { return bound.extends_down_to(value); },
220  m_bound);
221  }
222 
224  [[nodiscard]] constexpr bool extends_up_to(TYPE const &value) const
225  {
226  return std::visit(
227  [&value](auto const &bound) noexcept(noexcept(
228  bound.extends_up_to(value))) { return bound.extends_up_to(value); },
229  m_bound);
230  }
231 
233  [[nodiscard]] constexpr TYPE const *value() const & noexcept
234  {
235  return std::visit(
236  [](auto const &bound) noexcept {
237  using bound_t = std::decay_t<decltype(bound)>;
238  if constexpr (std::is_same_v<bound_t, no_bound>)
239  return static_cast<TYPE const *>(nullptr);
240  else
241  return &bound.get();
242  },
243  m_bound);
244  }
245 };
246 
247 
249 
267 template<typename TYPE> class range final
268 {
269  // (Putting private section first to work around bug in gcc < 10: see #665.)
270 private:
271  range_bound<TYPE> m_lower, m_upper;
272 
273 public:
275 
279  constexpr range(
280  range_bound<TYPE> lower, range_bound<TYPE> upper, sl loc = sl::current()) :
281  m_lower{lower}, m_upper{upper}
282  {
283  if (
284  lower.is_limited() and upper.is_limited() and
285  (*upper.value() < *lower.value()))
286  throw range_error{
287  std::format(
288  "Range's lower bound ({}) is greater than its upper bound ({}).",
289  to_string(*lower.value()), to_string(*upper.value())),
290  loc};
291  }
292 
294 
297  constexpr range() noexcept(noexcept(exclusive_bound<TYPE>{TYPE{}})) :
298  m_lower{exclusive_bound<TYPE>{TYPE{}}},
299  m_upper{exclusive_bound<TYPE>{TYPE{}}}
300  {}
301 
302  ~range() = default;
303 
304  constexpr bool operator==(range const &rhs) const noexcept(
305  noexcept(this->lower_bound() == rhs.lower_bound()) and
306  noexcept(this->upper_bound() == rhs.upper_bound()) and
307  noexcept(this->empty()))
308  {
309  return (this->lower_bound() == rhs.lower_bound() and
310  this->upper_bound() == rhs.upper_bound()) or
311  (this->empty() and rhs.empty());
312  }
313 
314  constexpr bool operator!=(range const &rhs) const
315  noexcept(noexcept(*this == rhs))
316  {
317  return not(*this == rhs);
318  }
319 
320  range(range const &) = default;
321  range(range &&) = default;
322  range &operator=(range const &) = default;
323  range &operator=(range &&) = default;
324 
326 
334  [[nodiscard]] constexpr bool empty() const noexcept(
335  noexcept(m_lower.is_exclusive()) and noexcept(m_lower.is_limited()) and
336  noexcept(*m_lower.value() < *m_upper.value()))
337  {
338  return (m_lower.is_exclusive() or m_upper.is_exclusive()) and
339  m_lower.is_limited() and m_upper.is_limited() and
340  not(*m_lower.value() < *m_upper.value());
341  }
342 
344  [[nodiscard]] constexpr bool contains(TYPE value) const noexcept(
345  noexcept(m_lower.extends_down_to(value)) and
346  noexcept(m_upper.extends_up_to(value)))
347  {
348  return m_lower.extends_down_to(value) and m_upper.extends_up_to(value);
349  }
350 
352 
355  [[nodiscard]] constexpr bool contains(range<TYPE> const &other) const
356  noexcept(noexcept((*this & other) == other))
357  {
358  return (*this & other) == other;
359  }
360 
361  [[nodiscard]] constexpr range_bound<TYPE> const &
362  lower_bound() const & noexcept
363  {
364  return m_lower;
365  }
366  [[nodiscard]] constexpr range_bound<TYPE> const &
367  upper_bound() const & noexcept
368  {
369  return m_upper;
370  }
371 
373 
375  constexpr range operator&(range const &other) const
376  {
377  range_bound<TYPE> lower{no_bound{}};
378  if (not this->lower_bound().is_limited())
379  lower = other.lower_bound();
380  else if (not other.lower_bound().is_limited())
381  lower = this->lower_bound();
382  else if (*this->lower_bound().value() < *other.lower_bound().value())
383  lower = other.lower_bound();
384  else if (*other.lower_bound().value() < *this->lower_bound().value())
385  lower = this->lower_bound();
386  else if (this->lower_bound().is_exclusive())
387  lower = this->lower_bound();
388  else
389  lower = other.lower_bound();
390 
391  range_bound<TYPE> upper{no_bound{}};
392  if (not this->upper_bound().is_limited())
393  upper = other.upper_bound();
394  else if (not other.upper_bound().is_limited())
395  upper = this->upper_bound();
396  else if (*other.upper_bound().value() < *this->upper_bound().value())
397  upper = other.upper_bound();
398  else if (*this->upper_bound().value() < *other.upper_bound().value())
399  upper = this->upper_bound();
400  else if (this->upper_bound().is_exclusive())
401  upper = this->upper_bound();
402  else
403  upper = other.upper_bound();
404 
405  if (
406  lower.is_limited() and upper.is_limited() and
407  (*upper.value() < *lower.value()))
408  return {};
409  else
410  return {lower, upper};
411  }
412 
413  // NOLINTBEGIN(google-explicit-constructor,hicpp-explicit-conversions)
414 
416  template<typename DEST> operator range<DEST>() const
417  {
418  range_bound<DEST> lower{no_bound{}}, upper{no_bound{}};
419  if (lower_bound().is_inclusive())
420  lower = inclusive_bound<DEST>{*lower_bound().value()};
421  else if (lower_bound().is_exclusive())
422  lower = exclusive_bound<DEST>{*lower_bound().value()};
423 
424  if (upper_bound().is_inclusive())
425  upper = inclusive_bound<DEST>{*upper_bound().value()};
426  else if (upper_bound().is_exclusive())
427  upper = exclusive_bound<DEST>{*upper_bound().value()};
428 
429  return {lower, upper};
430  }
431 
432  // NOLINTEND(google-explicit-constructor,hicpp-explicit-conversions)
433 };
434 
435 
437 
439 template<typename TYPE> struct string_traits<range<TYPE>> final
440 {
441  [[nodiscard]] static std::string_view
442  to_buf(std::span<char> buf, range<TYPE> const &value, ctx c = {})
443  {
444  if (value.empty())
445  {
446  if (std::cmp_less_equal(std::size(buf), std::size(s_empty)))
447  throw conversion_overrun{s_overrun.c_str(), c.loc};
448  return s_empty;
449  }
450  else
451  {
452  if (std::cmp_less(std::size(buf), 4))
453  throw conversion_overrun{s_overrun.c_str(), c.loc};
454  std::span<char> tmp{buf};
455  // C++26: Use at().
456  tmp[0] =
457  (static_cast<char>(value.lower_bound().is_inclusive() ? '[' : '('));
458  tmp = tmp.subspan(1);
459  TYPE const *lower{value.lower_bound().value()};
460  // Convert bound (but go back to overwrite that trailing zero).
461  if (lower != nullptr)
462  tmp = tmp.subspan(pqxx::into_buf(tmp, *lower));
463  // C++26: Use at().
464  tmp[0] = ',';
465  tmp = tmp.subspan(1);
466  TYPE const *upper{value.upper_bound().value()};
467  // Convert bound (but go back to overwrite that trailing zero).
468  if (upper != nullptr)
469  tmp = tmp.subspan(pqxx::into_buf(tmp, *upper, c));
470  if (std::cmp_less(std::size(tmp), 2))
471  throw conversion_overrun{s_overrun.c_str(), c.loc};
472  tmp[0] =
473  static_cast<char>(value.upper_bound().is_inclusive() ? ']' : ')');
474  tmp = tmp.subspan(1);
475  return {
476  std::data(buf),
477  static_cast<std::size_t>(std::data(tmp) - std::data(buf))};
478  }
479  }
480 
481  [[nodiscard]] static inline range<TYPE>
482  from_string(std::string_view text, sl loc = sl::current())
483  {
484  if (std::size(text) < 3)
485  throw pqxx::conversion_error{err_bad_input(text), loc};
486  switch (text[0])
487  {
488  case '[':
489  case '(': break;
490 
491  case 'e':
492  case 'E':
493  if (
494  (std::size(text) != std::size(s_empty)) or
495  (text[1] != 'm' and text[1] != 'M') or
496  (text[2] != 'p' and text[2] != 'P') or
497  (text[3] != 't' and text[3] != 'T') or
498  (text[4] != 'y' and text[4] != 'Y'))
499  throw pqxx::conversion_error{err_bad_input(text), loc};
500  return {};
501  break;
502 
503  default: throw pqxx::conversion_error{err_bad_input(text), loc};
504  }
505 
506  // The field parser uses this to track which field it's parsing, and
507  // when not to expect a field separator.
508  // clang-tidy rule bug:
509  // NOLINTNEXTLINE(misc-const-correctness)
510  std::size_t index{0};
511  // The last field we expect to see.
512  static constexpr std::size_t last{1};
513 
514  // Current parsing position. We skip the opening parenthesis or bracket.
515  // clang-tidy rule bug:
516  // NOLINTNEXTLINE(misc-const-correctness)
517  std::size_t pos{1};
518 
519  // The string may leave out either bound to indicate that it's unlimited.
520  std::optional<TYPE> lower, upper;
521  // We reuse the same field parser we use for composite values and arrays.
522  auto const field_parser{
523  pqxx::internal::specialize_parse_composite_field<std::optional<TYPE>>(
524  conversion_context{encoding_group::ascii_safe, loc})};
525  field_parser(index, text, pos, lower, last, loc);
526  field_parser(index, text, pos, upper, last, loc);
527 
528  // We need one more character: the closing parenthesis or bracket.
529  if (pos != std::size(text))
530  throw pqxx::conversion_error{err_bad_input(text), loc};
531  char const closing{text[pos - 1]};
532  if (closing != ')' and closing != ']')
533  throw pqxx::conversion_error{err_bad_input(text), loc};
534 
535  range_bound<TYPE> lower_bound{no_bound{}}, upper_bound{no_bound{}};
536  if (lower)
537  {
538  bool const left_inc{text[0] == '['};
539  if (left_inc)
540  lower_bound = inclusive_bound{*lower};
541  else
542  lower_bound = exclusive_bound{*lower};
543  }
544  if (upper)
545  {
546  bool const right_inc{closing == ']'};
547  if (right_inc)
548  upper_bound = inclusive_bound{*upper};
549  else
550  upper_bound = exclusive_bound{*upper};
551  }
552 
553  return {lower_bound, upper_bound};
554  }
555 
556  [[nodiscard]] static inline constexpr std::size_t
557  size_buffer(range<TYPE> const &value) noexcept
558  {
559  TYPE const *lower{value.lower_bound().value()},
560  *upper{value.upper_bound().value()};
561  std::size_t const lsz{
562  lower == nullptr ? 0 : pqxx::size_buffer(*lower) - 1},
563  usz{upper == nullptr ? 0 : pqxx::size_buffer(*upper) - 1};
564 
565  if (value.empty())
566  return std::size(s_empty) + 1;
567  else
568  return 1 + lsz + 1 + usz + 2;
569  }
570 
571 private:
572  static constexpr zview s_empty{"empty"_zv};
573  static constexpr auto s_overrun{"Not enough space in buffer for range."_zv};
574 
576  static std::string err_bad_input(std::string_view text)
577  {
578  return std::format("Invalid range input: '{}'.", text);
579  }
580 };
581 
582 
584 template<typename TYPE>
585 struct nullness<range<TYPE>> final : no_null<range<TYPE>>
586 {};
587 } // namespace pqxx
588 #endif
An exclusive boundary value to a pqxx::range.
Definition: range.hxx:94
constexpr bool extends_up_to(TYPE const &value) const noexcept(noexcept(value< m_value))
Would this bound, as an upper bound, include value?
Definition: range.hxx:122
constexpr bool extends_down_to(TYPE const &value) const noexcept(noexcept(m_value< value))
Would this bound, as a lower bound, include value?
Definition: range.hxx:115
constexpr exclusive_bound(TYPE const &value, sl loc=sl::current())
Definition: range.hxx:101
constexpr TYPE const & get() const &noexcept
Definition: range.hxx:109
An inclusive boundary value to a pqxx::range.
Definition: range.hxx:53
constexpr bool extends_down_to(TYPE const &value) const noexcept(noexcept(value< m_value))
Would this bound, as a lower bound, include value?
Definition: range.hxx:74
constexpr TYPE const & get() const &noexcept
Definition: range.hxx:68
constexpr inclusive_bound(TYPE const &value, sl loc=sl::current())
Definition: range.hxx:60
constexpr bool extends_up_to(TYPE const &value) const noexcept(noexcept(value< m_value))
Would this bound, as an upper bound, include value?
Definition: range.hxx:81
A range boundary value.
Definition: range.hxx:135
constexpr bool extends_up_to(TYPE const &value) const
Would this bound, as an upper bound, include value?
Definition: range.hxx:224
constexpr range_bound(range_bound const &) noexcept(noexcept(inclusive_bound< TYPE >{ std::declval< inclusive_bound< TYPE > const & >()}) and noexcept(exclusive_bound< TYPE >{ std::declval< exclusive_bound< TYPE > const & >()}))=default
constexpr bool is_exclusive() const noexcept
Is this boundary an exclusive one?
Definition: range.hxx:209
constexpr range_bound(inclusive_bound< TYPE > const &bound) noexcept(noexcept(inclusive_bound< TYPE >{bound}))
Definition: range.hxx:157
constexpr bool is_limited() const noexcept
Is this a finite bound?
Definition: range.hxx:197
constexpr bool operator==(range_bound const &rhs) const
Definition: range.hxx:178
constexpr bool operator!=(range_bound const &rhs) const noexcept(noexcept(*this==rhs))
Definition: range.hxx:188
constexpr bool extends_down_to(TYPE const &value) const
Would this bound, as a lower bound, include value?
Definition: range.hxx:215
range_bound & operator=(range_bound &&)=default
range_bound()=delete
constexpr range_bound(no_bound) noexcept
Definition: range.hxx:154
constexpr TYPE const * value() const &noexcept
Return bound value, or nullptr if it's not limited.
Definition: range.hxx:233
constexpr range_bound(exclusive_bound< TYPE > const &bound) noexcept(noexcept(exclusive_bound{bound}))
Definition: range.hxx:163
constexpr bool is_inclusive() const noexcept
Is this boundary an inclusive one?
Definition: range.hxx:203
~range_bound()=default
constexpr range_bound(range_bound &&)=default
range_bound & operator=(range_bound const &)=default
A C++ equivalent to PostgreSQL's range types.
Definition: range.hxx:268
constexpr range(range_bound< TYPE > lower, range_bound< TYPE > upper, sl loc=sl::current())
Create a range.
Definition: range.hxx:279
range & operator=(range const &)=default
constexpr range operator&(range const &other) const
Intersection of two ranges.
Definition: range.hxx:375
constexpr bool operator==(range const &rhs) const noexcept(noexcept(this->lower_bound()==rhs.lower_bound()) and noexcept(this->upper_bound()==rhs.upper_bound()) and noexcept(this->empty()))
Definition: range.hxx:304
constexpr bool contains(TYPE value) const noexcept(noexcept(m_lower.extends_down_to(value)) and noexcept(m_upper.extends_up_to(value)))
Does this range encompass value?
Definition: range.hxx:344
constexpr bool contains(range< TYPE > const &other) const noexcept(noexcept((*this &other)==other))
Does this range encompass all of other?
Definition: range.hxx:355
constexpr bool operator!=(range const &rhs) const noexcept(noexcept(*this==rhs))
Definition: range.hxx:314
range(range &&)=default
range(range const &)=default
constexpr range_bound< TYPE > const & upper_bound() const &noexcept
Definition: range.hxx:367
~range()=default
range & operator=(range &&)=default
constexpr range_bound< TYPE > const & lower_bound() const &noexcept
Definition: range.hxx:362
constexpr bool empty() const noexcept(noexcept(m_lower.is_exclusive()) and noexcept(m_lower.is_limited()) and noexcept(*m_lower.value()< *m_upper.value()))
Is this range clearly empty?
Definition: range.hxx:334
constexpr range() noexcept(noexcept(exclusive_bound< TYPE >{TYPE{}}))
Create an empty range.
Definition: range.hxx:297
Marker-type wrapper: zero-terminated std::string_view.
Definition: zview.hxx:55
Invalid argument passed to libpqxx, similar to std::invalid_argument.
Definition: except.hxx:599
Value conversion failed, e.g. when converting "Hello" to int.
Definition: except.hxx:612
Could not convert value to string: not enough buffer space.
Definition: except.hxx:638
Something is out of range, similar to std::out_of_range.
Definition: except.hxx:651
The home of all libpqxx classes, functions, templates, etc.
Definition: array.cxx:26
std::source_location sl
Convenience alias for std::source_location. It's just too long.
Definition: types.hxx:38
PQXX_LIBEXPORT std::string to_string(field_ref const &value, ctx)
Convert a field_ref to a string.
Definition: field.hxx:891
constexpr std::size_t size_buffer(TYPE const &...value) noexcept
Estimate how much buffer space is needed to represent values as a string.
Definition: strconv.hxx:399
constexpr bool is_null(TYPE const &value) noexcept
Is value a null?
Definition: strconv.hxx:764
requires(pqxx::internal::to_buf_7< TYPE > or pqxx::internal::to_buf_8< TYPE >) const eval bool supports_to_buf_8()
Is the libpqxx 8 version of to_buf() supported for TYPE?
Definition: strconv.hxx:417
std::size_t into_buf(std::span< char > buf, TYPE const &value, ctx c={})
Write an SQL representation of value into buf.
Definition: strconv.hxx:454
concept has_less
Concept: T supports copying, and less-than operator.
Definition: range.hxx:40
concept has_equal
Concept: T supports equality comparison.
Definition: range.hxx:45
conversion_context const & ctx
Convenience alias: const reference to a pqxx::conversion_context.
Definition: strconv.hxx:201
format
Format code: is data text or binary?
Definition: types.hxx:121
Contextual parameters for string conversions implementations.
Definition: strconv.hxx:163
An unlimited boundary value to a pqxx::range.
Definition: range.hxx:24
constexpr bool extends_down_to(TYPE const &) const noexcept
Definition: range.hxx:26
constexpr bool extends_up_to(TYPE const &) const noexcept
Definition: range.hxx:31
Nullness traits describing a type which does not have a null value.
Definition: strconv.hxx:93
Traits describing a type's "null value," if any.
Definition: strconv.hxx:70
static range< TYPE > from_string(std::string_view text, sl loc=sl::current())
Definition: range.hxx:482
static constexpr std::size_t size_buffer(range< TYPE > const &value) noexcept
Definition: range.hxx:557
static std::string_view to_buf(std::span< char > buf, range< TYPE > const &value, ctx c={})
Definition: range.hxx:442
Traits class for use in string conversions.
Definition: strconv.hxx:213