microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
ranges.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
9#include "../microfmt.hpp"
10#include <iterator>
11#include <string_view>
12#include <type_traits>
13
14namespace microfmt {
15
16namespace detail {
17
18template <typename T, typename = void> struct is_range : std::false_type {};
19
20template <typename T>
21struct is_range<
22 T, std::void_t<decltype(std::begin(std::declval<const T &>())), decltype(std::end(std::declval<const T &>()))>>
23 : std::true_type {};
24
25#if RELOCO_CXX20
26// Fixed string helper for compile-time format string & delimiter NTTPs
27template <size_t N> struct fixed_string {
28 char buf[N + 1]{};
29 size_t size{N};
30
31 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE
32
33 // Implicit on purpose: a string literal must convert to the class-type NTTP (`join_as<", ">(r)`).
34 constexpr fixed_string(const char (&str)[N + 1]) noexcept { // NOLINT(reloco-explicit-conversion)
35 for (size_t i = 0; i < N; ++i) {
36 buf[i] = str[i];
37 }
38 buf[N] = '\0';
39 }
40
41 RELOCO_END_UNSAFE_BUFFER_USAGE
42
43 [[nodiscard]] constexpr microfmt::string_view view() const & noexcept RELOCO_LIFETIMEBOUND {
44 return microfmt::string_view(buf, N);
45 }
46
47 [[nodiscard]] constexpr microfmt::string_view view() const && = delete;
48};
49
50template <size_t N> fixed_string(const char (&)[N]) -> fixed_string<N - 1>;
51#endif
52
53} // namespace detail
54
55// ============================================================================
56// Join View Adapter (Iterator Pair or Range Reference)
57// ============================================================================
58
59template <typename It, typename Sentinel = It> struct RELOCO_POINTER join_view {
61 Sentinel last;
62 microfmt::string_view delimiter{", "};
63 // Custom per-element specifier (e.g. "02x")
64};
65
66#if RELOCO_CXX20
67// ============================================================================
68// Zero-Size Specifier join_as_view (Compile-Time NTTPs)
69// ============================================================================
70
71template <typename It, typename Sentinel, detail::fixed_string Delim, detail::fixed_string ElemSpec>
72struct join_as_view {
73 It first;
74 Sentinel last;
75};
76#endif
77
78// ============================================================================
79// Factory Functions
80// ============================================================================
81
82// Standard join view
83template <typename It, typename Sentinel,
84 std::enable_if_t<!std::is_convertible_v<Sentinel, microfmt::string_view>, int> = 0>
85[[nodiscard]] constexpr auto join(It first, Sentinel last, microfmt::string_view delimiter = ", ") noexcept {
86 return join_view<It, Sentinel>{first, last, delimiter};
87}
88
89template <typename Range, std::enable_if_t<detail::is_range<Range>::value, int> = 0>
90[[nodiscard]] constexpr auto join(const Range &range, microfmt::string_view delimiter = ", ") noexcept {
91 using std::begin;
92 using std::end;
93 return join_view<decltype(begin(range)), decltype(end(range))>{begin(range), end(range), delimiter};
94}
95
96template <typename Range,
97 std::enable_if_t<
98 !std::is_lvalue_reference_v<Range> && detail::is_range<std::remove_reference_t<Range>>::value, int> = 0>
99[[nodiscard]] constexpr auto join(Range &&, microfmt::string_view = ", ") noexcept = delete;
100
101#if RELOCO_CXX20
102// Compile-time join_as (zero runtime overhead)
103template <detail::fixed_string Delim, detail::fixed_string ElemSpec = "", typename Range,
104 std::enable_if_t<detail::is_range<Range>::value, int> = 0>
105[[nodiscard]] constexpr auto join_as(const Range &range) noexcept {
106 using std::begin;
107 using std::end;
108 return join_as_view<decltype(begin(range)), decltype(end(range)), Delim, ElemSpec>{begin(range), end(range)};
109}
110
111template <detail::fixed_string Delim, detail::fixed_string ElemSpec = "", typename Range,
112 std::enable_if_t<
113 !std::is_lvalue_reference_v<Range> && detail::is_range<std::remove_reference_t<Range>>::value, int> = 0>
114[[nodiscard]] constexpr auto join_as(Range &&) noexcept = delete;
115
116template <detail::fixed_string Delim, detail::fixed_string ElemSpec = "", typename It, typename Sentinel,
117 std::enable_if_t<!std::is_convertible_v<Sentinel, microfmt::string_view>, int> = 0>
118[[nodiscard]] constexpr auto join_as(It first, Sentinel last) noexcept {
119 return join_as_view<It, Sentinel, Delim, ElemSpec>{first, last};
120}
121#endif
122
123// ============================================================================
124// Formatter for join_view (Receives element spec from format string {:02x})
125// ============================================================================
126
127template <typename It, typename Sentinel> struct formatter<join_view<It, Sentinel>> {
128 microfmt::string_view elem_spec{""};
129
130 constexpr void parse(format_parse_context &ctx) noexcept { elem_spec = ctx.spec(); }
131
132 void format(const join_view<It, Sentinel> &jv, const sink &out) const noexcept {
133 using ValueType = std::remove_cv_t<std::remove_reference_t<decltype(*std::declval<It>())>>;
134
136 format_parse_context elem_ctx(elem_spec);
137 element_fmt.parse(elem_ctx);
138
139 bool is_first = true;
140 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
141
142 for (auto it = jv.first; it != jv.last; ++it) {
143 if (!is_first) {
144 out.write(jv.delimiter);
145 }
146 is_first = false;
147 element_fmt.format(*it, out);
148 }
149 RELOCO_END_UNSAFE_BUFFER_USAGE;
150 }
151};
152
153#if RELOCO_CXX20
154// ============================================================================
155// Formatter for join_as_view (Zero runtime state overhead)
156// ============================================================================
157
158template <typename It, typename Sentinel, detail::fixed_string Delim, detail::fixed_string ElemSpec>
159struct formatter<join_as_view<It, Sentinel, Delim, ElemSpec>> {
160 microfmt::string_view runtime_spec{""};
161
162 constexpr void parse(format_parse_context &ctx) noexcept { runtime_spec = ctx.spec(); }
163
164 void format(const join_as_view<It, Sentinel, Delim, ElemSpec> &jv, const sink &out) const noexcept {
165 using ValueType = std::remove_cv_t<std::remove_reference_t<decltype(*std::declval<It>())>>;
166
167 constexpr microfmt::string_view ct_elem_spec = ElemSpec.view();
168 const microfmt::string_view effective_spec = !ct_elem_spec.empty() ? ct_elem_spec : runtime_spec;
169
170 detail::element_formatter<ValueType> element_fmt;
171 format_parse_context elem_ctx(effective_spec);
172 element_fmt.parse(elem_ctx);
173
174 bool is_first = true;
175 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
176
177 for (auto it = jv.first; it != jv.last; ++it) {
178 if (!is_first) {
179 out.write(Delim.view());
180 }
181 is_first = false;
182 element_fmt.format(*it, out);
183 }
184
185 RELOCO_END_UNSAFE_BUFFER_USAGE;
186 }
187};
188#endif
189
190} // namespace microfmt
Definition microfmt.hpp:881
RELOCO_END_UNSAFE_CODE auto format(microfmt::string_view fmt_str, const Args &...args) noexcept
Definition fmt.hpp:178
Definition assert.hpp:19
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
constexpr joined_span_view< T > join(span< const T > items, microfmt::string_view delim=", ") noexcept
Definition format_helpers.hpp:239
Drop-in replacement for a formatter<T> underlying_formatter; member used by container/wrapper formatt...
Definition microfmt.hpp:1433
constexpr void parse(format_parse_context &ctx) noexcept
Definition microfmt.hpp:1445
void format(const T &value, const sink &out) const noexcept
Definition microfmt.hpp:1447
Definition ranges.hpp:18
void format(const join_view< It, Sentinel > &jv, const sink &out) const noexcept
Definition ranges.hpp:132
constexpr void parse(format_parse_context &ctx) noexcept
Definition ranges.hpp:130
Definition microfmt.hpp:985
Definition ranges.hpp:59
It first
Definition ranges.hpp:60
Sentinel last
Definition ranges.hpp:61