Skip to content

Commit c27953f

Browse files
committed
🐛 Fix formatting of move-only types
Problem: - Formatting of move-only types does not work. Solution: - Move the values through the formatting machinery appropriately.
1 parent 8ace16b commit c27953f

2 files changed

Lines changed: 68 additions & 19 deletions

File tree

include/stdx/ct_format.hpp

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ struct format_result<Str, Args, Spans> {
109109

110110
template <typename Spans = type_list<>, typename Str, typename Args = tuple<>>
111111
constexpr auto make_format_result(Str s, Args args = {}) {
112-
return format_result<Str, Args, Spans>{s, args};
112+
return format_result<Str, Args, Spans>{s, std::move(args)};
113113
}
114114

115115
inline namespace literals {
@@ -228,13 +228,13 @@ CONSTEVAL auto arg_type(fmt_cx_value auto a) {
228228

229229
template <typename Str, typename Args, typename Spans, typename S>
230230
constexpr auto operator+(format_result<Str, Args, Spans> r, S s) {
231-
return make_format_result<Spans>(r.str + s, r.args);
231+
return make_format_result<Spans>(r.str + s, std::move(r.args));
232232
}
233233

234234
template <typename S, typename Str, typename Args, typename Spans>
235235
constexpr auto operator+(S s, format_result<Str, Args, Spans> r) {
236-
return make_format_result<detail::apply_offset<s.size(), Spans>>(s + r.str,
237-
r.args);
236+
return make_format_result<detail::apply_offset<s.size(), Spans>>(
237+
s + r.str, std::move(r.args));
238238
}
239239

240240
template <typename Str1, typename Args1, typename Spans1, typename Str2,
@@ -243,7 +243,7 @@ constexpr auto operator+(format_result<Str1, Args1, Spans1> r1,
243243
format_result<Str2, Args2, Spans2> r2) {
244244
return make_format_result<boost::mp11::mp_append<
245245
Spans1, detail::apply_offset<r1.str.size(), Spans2>>>(
246-
r1.str + r2.str, tuple_cat(r1.args, r2.args));
246+
r1.str + r2.str, tuple_cat(std::move(r1.args), std::move(r2.args)));
247247
}
248248

249249
template <typename T, T...> struct null_output;
@@ -271,7 +271,7 @@ CONSTEVAL auto convert_output() {
271271
}
272272

273273
template <std::size_t N>
274-
CONSTEVAL auto perform_format(auto s, auto v) -> ct_string<N + 1> {
274+
CONSTEVAL auto perform_format(auto s, auto const &v) -> ct_string<N + 1> {
275275
ct_string<N + 1> cts{};
276276
fmt::format_to(cts.begin(), s, v);
277277
return cts;
@@ -302,10 +302,10 @@ constexpr auto format1(Arg arg) {
302302
auto const sub_result = format1<Fmt, Start>(arg.str);
303303
using Spans = typename Arg::spans_t;
304304
return make_format_result<detail::apply_offset<Start, Spans>>(
305-
sub_result.str, arg.args);
305+
sub_result.str, std::move(arg).args);
306306
} else {
307307
using Spans = type_list<format_span<Start, Fmt.size()>>;
308-
return make_format_result<Spans>(cts_t<Fmt>{}, tuple{arg});
308+
return make_format_result<Spans>(cts_t<Fmt>{}, tuple{std::move(arg)});
309309
}
310310
}
311311

@@ -324,10 +324,20 @@ template <ct_string Fmt> struct fmt_data {
324324
to_ct_string<splits[N].view.size()>(splits[N].view);
325325
};
326326

327-
template <typename T>
328-
constexpr auto ct_format_as(T const &t) -> decltype(auto) {
329-
return (t);
330-
}
327+
[[maybe_unused]] constexpr inline struct format_as_t {
328+
template <typename T>
329+
requires true
330+
constexpr auto operator()(T &&t) const
331+
noexcept(noexcept(ct_format_as(std::forward<T>(t))))
332+
-> decltype(ct_format_as(std::forward<T>(t))) {
333+
return ct_format_as(std::forward<T>(t));
334+
}
335+
336+
template <typename T>
337+
constexpr auto operator()(T &&t) const -> decltype(auto) {
338+
return T(std::forward<T>(t));
339+
}
340+
} format_as;
331341
} // namespace detail
332342

333343
template <ct_string Fmt,
@@ -343,20 +353,20 @@ constexpr auto ct_format = [](auto &&...args) {
343353
"Format string has a mismatch between the number of format "
344354
"specifiers and arguments.");
345355

346-
[[maybe_unused]] auto const format1 = [&]<std::size_t I>(auto &&arg) {
356+
[[maybe_unused]] auto const format1 = []<std::size_t I>(auto &&arg) {
347357
constexpr auto cts = detail::to_ct_string<data::splits[I].view.size()>(
348358
data::splits[I].view);
349359
return detail::format1<cts, data::splits[I].start>(FWD(arg));
350360
};
351361

352-
auto const result = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
353-
using detail::ct_format_as;
354-
return (format1.template operator()<Is>(ct_format_as(FWD(args))) + ... +
355-
make_format_result(cts_t<data::last_cts>{}));
356-
}(std::make_index_sequence<data::N>{});
362+
auto result = [&]<std::size_t... Is>(std::index_sequence<Is...>,
363+
auto &&...as) {
364+
return (format1.template operator()<Is>(detail::format_as(FWD(as))) +
365+
... + make_format_result(cts_t<data::last_cts>{}));
366+
}(std::make_index_sequence<data::N>{}, FWD(args)...);
357367
constexpr auto str = detail::convert_output<result.str.value, Output>();
358368
using Spans = typename std::remove_cvref_t<decltype(result)>::spans_t;
359-
return make_format_result<Spans>(str, result.args);
369+
return make_format_result<Spans>(str, std::move(result).args);
360370
};
361371

362372
template <ct_string Fmt>

test/ct_format.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include "detail/tuple_types.hpp"
2+
13
#include <stdx/ct_format.hpp>
24
#include <stdx/utility.hpp>
35

@@ -112,6 +114,35 @@ TEST_CASE("format a char (CX_VALUE)", "[ct_format]") {
112114
"Hello world"_ctst, stdx::tuple{stdx::ct_format_arg<char>{}}));
113115
}
114116

117+
#ifndef STDX_FREESTANDING
118+
namespace move_test {
119+
struct move_only {
120+
constexpr move_only() = default;
121+
constexpr move_only(int x) : value{x} {}
122+
constexpr move_only(move_only &&) = default;
123+
constexpr auto operator=(move_only &&) noexcept -> move_only & = default;
124+
125+
friend constexpr auto operator==(move_only const &, move_only const &)
126+
-> bool = default;
127+
128+
int value;
129+
};
130+
131+
[[nodiscard]] constexpr auto format_as(move_only const &) -> std::string_view {
132+
return "17";
133+
}
134+
} // namespace move_test
135+
136+
TEST_CASE("format a move-only argument (CX_VALUE)", "[ct_format]") {
137+
using expected_spans_t = stdx::type_list<stdx::format_span<6, 8>>;
138+
STATIC_CHECK(
139+
stdx::ct_format<"Hello {}">(CX_VALUE(move_test::move_only{17})) ==
140+
stdx::make_format_result<expected_spans_t>(
141+
"Hello 17"_ctst,
142+
stdx::tuple{stdx::ct_format_arg<move_test::move_only>{}}));
143+
}
144+
#endif
145+
115146
TEST_CASE("format a compile-time integral argument (ct)", "[ct_format]") {
116147
using expected_spans_t = stdx::type_list<stdx::format_span<6, 8>>;
117148
STATIC_CHECK(stdx::ct_format<"Hello {}">(stdx::ct<42>()) ==
@@ -193,6 +224,14 @@ TEST_CASE("format a runtime argument", "[ct_format]") {
193224
STATIC_CHECK(stdx::ct_format<"Hello {}">(x) == expected);
194225
}
195226

227+
TEST_CASE("format a move-only runtime argument", "[ct_format]") {
228+
using expected_spans_t = stdx::type_list<stdx::format_span<6, 8>>;
229+
constexpr auto expected = stdx::make_format_result<expected_spans_t>(
230+
"Hello {}"_ctst, stdx::tuple{move_only{17}});
231+
CHECK(stdx::ct_format<"Hello {}">(move_only{17}) == expected);
232+
STATIC_CHECK(stdx::ct_format<"Hello {}">(move_only{17}) == expected);
233+
}
234+
196235
TEST_CASE("format a compile-time and a runtime argument (1)", "[ct_format]") {
197236
constexpr auto x = 17;
198237
using expected_spans_t =

0 commit comments

Comments
 (0)