Skip to content

Commit 48479de

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 48479de

2 files changed

Lines changed: 63 additions & 17 deletions

File tree

include/stdx/ct_format.hpp

Lines changed: 26 additions & 17 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,
@@ -349,14 +359,13 @@ constexpr auto ct_format = [](auto &&...args) {
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>{}));
362+
auto result = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
363+
return (format1.template operator()<Is>(detail::format_as(FWD(args))) +
364+
... + make_format_result(cts_t<data::last_cts>{}));
356365
}(std::make_index_sequence<data::N>{});
357366
constexpr auto str = detail::convert_output<result.str.value, Output>();
358367
using Spans = typename std::remove_cvref_t<decltype(result)>::spans_t;
359-
return make_format_result<Spans>(str, result.args);
368+
return make_format_result<Spans>(str, std::move(result).args);
360369
};
361370

362371
template <ct_string Fmt>

test/ct_format.cpp

Lines changed: 37 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,33 @@ TEST_CASE("format a char (CX_VALUE)", "[ct_format]") {
112114
"Hello world"_ctst, stdx::tuple{stdx::ct_format_arg<char>{}}));
113115
}
114116

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

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

0 commit comments

Comments
 (0)