Skip to content

Commit b271216

Browse files
committed
🎨 Support named parameters to ct_format
Problem: - Parameters cannot be named (for identification on the back end of CIB for instance). - Existing argument span information is unwieldy; the `tuple`/`type_list` sizes get big. Solution: - Support naming parameters in the format string. e.g. "Hello {world}" has one parameter named "world". `STDX_CT_FORMAT("Hello {world}", 42)` results in a `format_result` containing `"Hello 42"` and a list of named arguments (`type_list<named_arg<"world", int, 6, 8>>` - containing the name, type, and span extents of the argument). - Named runtime arguments come out as `named_arg<"world", int, 0>` - the name, the type, and the index in the runtime arg tuple. Note: - While parameters in the string are named, arguments to `ct_format` are still positional.
1 parent fb77401 commit b271216

2 files changed

Lines changed: 410 additions & 272 deletions

File tree

include/stdx/ct_format.hpp

Lines changed: 128 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -28,45 +28,51 @@ template <std::size_t N> constexpr auto format_as(stdx::ct_string<N> const &s) {
2828
return std::string_view{s};
2929
}
3030

31-
template <std::size_t Begin, std::size_t End> struct format_span {
31+
template <stdx::ct_string Name, typename T, int Begin, int End = Begin - 1>
32+
struct named_arg {
33+
using type = T;
34+
constexpr static auto name = Name;
3235
constexpr static auto begin = Begin;
3336
constexpr static auto end = End;
3437

38+
constexpr static std::integral_constant<bool, Begin >= End> is_runtime{};
39+
40+
template <int StringOffset, int ArgOffset>
41+
CONSTEVAL static auto apply_offset() {
42+
if constexpr (is_runtime) {
43+
return named_arg<Name, T, Begin + ArgOffset, End + ArgOffset>{};
44+
} else {
45+
return named_arg<Name, T, Begin + StringOffset,
46+
End + StringOffset>{};
47+
}
48+
}
49+
3550
private:
36-
friend constexpr auto operator==(format_span const &, format_span const &)
51+
friend constexpr auto operator==(named_arg const &, named_arg const &)
3752
-> bool = default;
3853
};
3954

4055
namespace detail {
41-
template <std::size_t Offset> struct apply_span_offset_q {
42-
template <typename Span>
43-
using fn = format_span<Offset + Span::begin, Offset + Span::end>;
56+
template <std::size_t StringOffset, std::size_t ArgOffset>
57+
struct apply_span_offset_q {
58+
template <typename Arg>
59+
using fn = decltype(Arg::template apply_offset<StringOffset, ArgOffset>());
4460
};
4561

46-
template <std::size_t Offset, typename L>
62+
template <std::size_t StringOffset, std::size_t ArgOffset, typename L>
4763
using apply_offset =
48-
boost::mp11::mp_transform_q<apply_span_offset_q<Offset>, L>;
64+
boost::mp11::mp_transform_q<apply_span_offset_q<StringOffset, ArgOffset>,
65+
L>;
4966
} // namespace detail
5067

51-
template <typename T = void> struct ct_format_arg {
52-
using type_t = T;
53-
friend constexpr auto operator==(ct_format_arg const &,
54-
ct_format_arg const &) -> bool = default;
55-
};
56-
57-
template <typename Arg>
58-
using is_compile_time_arg =
59-
std::bool_constant<is_specialization_of<Arg, ct_format_arg>()>;
60-
61-
template <typename Str, typename Args, typename Spans> struct format_result {
62-
static_assert(Args::size() == boost::mp11::mp_size<Spans>::value);
63-
68+
template <typename Str, typename Args, typename NamedArgs>
69+
struct format_result {
6470
CONSTEVAL static auto ct_string_convertible()
6571
-> std::bool_constant<Args::size() == 0>;
6672

6773
[[no_unique_address]] Str str;
6874
[[no_unique_address]] Args args{};
69-
using spans_t = Spans;
75+
using named_args_t = NamedArgs;
7076

7177
friend constexpr auto operator+(format_result const &fr)
7278
requires(decltype(ct_string_convertible())::value)
@@ -85,17 +91,14 @@ template <typename Str, typename Args, typename Spans> struct format_result {
8591
format_result const &) -> bool = default;
8692
};
8793

88-
template <typename Str, typename Args, typename Spans>
89-
requires(boost::mp11::mp_all_of<Args, is_compile_time_arg>::value and
90-
is_cx_value_v<Str>)
91-
struct format_result<Str, Args, Spans> {
92-
static_assert(Args::size() == boost::mp11::mp_size<Spans>::value);
93-
94+
template <typename Str, typename Args, typename NamedArgs>
95+
requires(Args::size() == 0 and is_cx_value_v<Str>)
96+
struct format_result<Str, Args, NamedArgs> {
9497
CONSTEVAL static auto ct_string_convertible() -> std::true_type;
9598

9699
[[no_unique_address]] Str str;
97100
[[no_unique_address]] Args args{};
98-
using spans_t = Spans;
101+
using named_args_t = NamedArgs;
99102

100103
friend constexpr auto operator+(format_result const &fr) { return +fr.str; }
101104

@@ -107,9 +110,10 @@ struct format_result<Str, Args, Spans> {
107110
format_result const &) -> bool = default;
108111
};
109112

110-
template <typename Spans = type_list<>, typename Str, typename Args = tuple<>>
113+
template <typename NamedArgs = type_list<>, typename Str,
114+
typename Args = tuple<>>
111115
constexpr auto make_format_result(Str s, Args args = {}) {
112-
return format_result<Str, Args, Spans>{s, std::move(args)};
116+
return format_result<Str, Args, NamedArgs>{s, std::move(args)};
113117
}
114118

115119
inline namespace literals {
@@ -180,6 +184,50 @@ CONSTEVAL auto split_specifiers(std::string_view fmt)
180184
return splits;
181185
}
182186

187+
template <ct_string S, std::size_t Start> CONSTEVAL auto extract_format1_str() {
188+
constexpr auto name_start = Start + 1;
189+
constexpr auto it = [] {
190+
for (auto i = S.value.cbegin() + name_start; i != S.value.cend(); ++i) {
191+
if (*i == ':') {
192+
return i;
193+
}
194+
}
195+
return S.value.cend();
196+
}();
197+
if constexpr (it == S.value.cend()) {
198+
if constexpr (Start == S.size() - 2) {
199+
// no name, empty fmt spec e.g. "abc{}"
200+
return std::pair{S, ct_string{""}};
201+
} else {
202+
// named arg, empty fmt spec, e.g. "abc{ghi}"
203+
constexpr auto suffix_start = S.size() - 1;
204+
constexpr auto prefix_size = name_start;
205+
constexpr auto name_size = suffix_start - name_start;
206+
constexpr auto suffix_size = 1;
207+
208+
return std::pair{
209+
ct_string<prefix_size + 1U>{S.value.cbegin(), prefix_size} +
210+
ct_string<suffix_size + 1U>{S.value.cbegin() + suffix_start,
211+
suffix_size},
212+
ct_string<name_size + 1U>{S.value.cbegin() + name_start,
213+
name_size}};
214+
}
215+
} else {
216+
// named arg, fmt spec, e.g. "abc{ghi:x}"
217+
constexpr auto suffix_start = it - S.value.cbegin();
218+
constexpr auto prefix_size = name_start;
219+
constexpr auto name_size = suffix_start - name_start;
220+
constexpr auto suffix_size = S.size() - suffix_start;
221+
222+
return std::pair{
223+
ct_string<prefix_size + 1U>{S.value.cbegin(), prefix_size} +
224+
ct_string<suffix_size + 1U>{S.value.cbegin() + suffix_start,
225+
suffix_size},
226+
ct_string<name_size + 1U>{S.value.cbegin() + name_start,
227+
name_size}};
228+
}
229+
}
230+
183231
template <typename T>
184232
concept fmt_cx_value =
185233
is_cx_value_v<T> or requires(T t) { ct_string_from_type(t); };
@@ -226,23 +274,27 @@ CONSTEVAL auto arg_type(fmt_cx_value auto a) {
226274
}
227275
}
228276

229-
template <typename Str, typename Args, typename Spans, typename S>
230-
constexpr auto operator+(format_result<Str, Args, Spans> r, S s) {
231-
return make_format_result<Spans>(r.str + s, std::move(r.args));
277+
template <typename Str, typename Args, typename NamedArgs, typename S>
278+
constexpr auto operator+(format_result<Str, Args, NamedArgs> r, S s) {
279+
return make_format_result<NamedArgs>(r.str + s, std::move(r.args));
232280
}
233281

234-
template <typename S, typename Str, typename Args, typename Spans>
235-
constexpr auto operator+(S s, format_result<Str, Args, Spans> r) {
236-
return make_format_result<detail::apply_offset<s.size(), Spans>>(
282+
template <typename S, typename Str, typename Args, typename NamedArgs>
283+
constexpr auto operator+(S s, format_result<Str, Args, NamedArgs> r) {
284+
return make_format_result<detail::apply_offset<s.size(), 0, NamedArgs>>(
237285
s + r.str, std::move(r.args));
238286
}
239287

240-
template <typename Str1, typename Args1, typename Spans1, typename Str2,
241-
typename Args2, typename Spans2>
242-
constexpr auto operator+(format_result<Str1, Args1, Spans1> r1,
243-
format_result<Str2, Args2, Spans2> r2) {
244-
return make_format_result<boost::mp11::mp_append<
245-
Spans1, detail::apply_offset<r1.str.size(), Spans2>>>(
288+
template <typename Str1, typename Args1, typename NamedArgs1, typename Str2,
289+
typename Args2, typename NamedArgs2>
290+
constexpr auto operator+(format_result<Str1, Args1, NamedArgs1> r1,
291+
format_result<Str2, Args2, NamedArgs2> r2) {
292+
using ShiftedNamedArgs2 =
293+
detail::apply_offset<r1.str.size(), boost::mp11::mp_size<Args1>::value,
294+
NamedArgs2>;
295+
296+
return make_format_result<
297+
boost::mp11::mp_append<NamedArgs1, ShiftedNamedArgs2>>(
246298
r1.str + r2.str,
247299
stdx::tuple_cat(std::move(r1.args), std::move(r2.args)));
248300
}
@@ -278,35 +330,44 @@ CONSTEVAL auto perform_format(auto s, auto const &v) -> ct_string<N + 1> {
278330
return cts;
279331
}
280332

281-
template <ct_string Fmt, std::size_t Start, typename Arg>
333+
template <ct_string Fmt, ct_string Name, std::size_t Start, typename Arg>
282334
constexpr auto format1(Arg arg) {
283335
if constexpr (requires { arg_value(arg); }) {
284336
constexpr auto fmtstr = STDX_FMT_COMPILE(Fmt);
285337
constexpr auto a = arg_value(arg);
286-
if constexpr (is_specialization_of_v<std::remove_cv_t<decltype(a)>,
287-
format_result>) {
338+
using a_t = std::remove_cv_t<decltype(a)>;
339+
if constexpr (is_specialization_of_v<a_t, format_result>) {
288340
constexpr auto s = convert_input(a.str);
289341
constexpr auto sz = fmt::formatted_size(fmtstr, s);
290342
constexpr auto cts = perform_format<sz>(fmtstr, s);
291-
using Spans = typename std::remove_cvref_t<decltype(a)>::spans_t;
292-
return make_format_result<detail::apply_offset<Start, Spans>>(
293-
cts_t<cts>{}, a.args);
343+
using shifted_named_args_t =
344+
detail::apply_offset<Start, 0, typename a_t::named_args_t>;
345+
return make_format_result<shifted_named_args_t>(cts_t<cts>{},
346+
a.args);
294347
} else {
295-
using arg_t = stdx::remove_cvref_t<decltype(arg_type(arg))>;
296348
constexpr auto sz = fmt::formatted_size(fmtstr, a);
297349
constexpr auto cts = perform_format<sz>(fmtstr, a);
298-
using Spans = type_list<format_span<Start, sz>>;
299-
return make_format_result<Spans>(cts_t<cts>{},
300-
tuple{ct_format_arg<arg_t>{}});
350+
if constexpr (not Name.empty()) {
351+
using name_info_t = named_arg<Name, a_t, Start, sz>;
352+
return make_format_result<type_list<name_info_t>>(cts_t<cts>{});
353+
} else {
354+
return make_format_result(cts_t<cts>{});
355+
}
301356
}
302357
} else if constexpr (is_specialization_of_v<Arg, format_result>) {
303-
auto const sub_result = format1<Fmt, Start>(arg.str);
304-
using Spans = typename Arg::spans_t;
305-
return make_format_result<detail::apply_offset<Start, Spans>>(
306-
sub_result.str, std::move(arg).args);
358+
auto const sub_result = format1<Fmt, "", Start>(arg.str);
359+
using shifted_named_args_t =
360+
detail::apply_offset<Start, 0, typename Arg::named_args_t>;
361+
return make_format_result<shifted_named_args_t>(sub_result.str,
362+
std::move(arg).args);
307363
} else {
308-
using Spans = type_list<format_span<Start, Fmt.size()>>;
309-
return make_format_result<Spans>(cts_t<Fmt>{}, tuple{std::move(arg)});
364+
if constexpr (not Name.empty()) {
365+
using name_info_t = named_arg<Name, Arg, 0>;
366+
return make_format_result<type_list<name_info_t>>(
367+
cts_t<Fmt>{}, tuple{std::move(arg)});
368+
} else {
369+
return make_format_result(cts_t<Fmt>{}, tuple{std::move(arg)});
370+
}
310371
}
311372
}
312373

@@ -355,9 +416,12 @@ constexpr auto ct_format = [](auto &&...args) {
355416
"specifiers and arguments.");
356417

357418
[[maybe_unused]] auto const format1 = []<std::size_t I>(auto &&arg) {
358-
constexpr auto cts = detail::to_ct_string<data::splits[I].view.size()>(
359-
data::splits[I].view);
360-
return detail::format1<cts, data::splits[I].start>(FWD(arg));
419+
constexpr auto fmt = detail::extract_format1_str<
420+
detail::to_ct_string<data::splits[I].view.size()>(
421+
data::splits[I].view),
422+
data::splits[I].start>();
423+
return detail::format1<fmt.first, fmt.second, data::splits[I].start>(
424+
FWD(arg));
361425
};
362426

363427
auto result = [&]<std::size_t... Is>(std::index_sequence<Is...>,
@@ -366,8 +430,9 @@ constexpr auto ct_format = [](auto &&...args) {
366430
... + make_format_result(cts_t<data::last_cts>{}));
367431
}(std::make_index_sequence<data::N>{}, FWD(args)...);
368432
constexpr auto str = detail::convert_output<result.str.value, Output>();
369-
using Spans = typename std::remove_cvref_t<decltype(result)>::spans_t;
370-
return make_format_result<Spans>(str, std::move(result).args);
433+
using NamedArgs =
434+
typename std::remove_cvref_t<decltype(result)>::named_args_t;
435+
return make_format_result<NamedArgs>(str, std::move(result).args);
371436
};
372437

373438
template <ct_string Fmt>

0 commit comments

Comments
 (0)