Implement fmt::join for tuple-like objects (#4230)

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
This commit is contained in:
Vladislav Shchapov 2024-11-09 21:28:46 +05:00 committed by GitHub
parent 542600013f
commit 5a3576acc8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 34 additions and 24 deletions

View File

@ -696,18 +696,18 @@ auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
* fmt::print("{:02}", fmt::join(v, ", ")); * fmt::print("{:02}", fmt::join(v, ", "));
* // Output: 01, 02, 03 * // Output: 01, 02, 03
*/ */
template <typename Range> template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
auto join(Range&& r, string_view sep) auto join(Range&& r, string_view sep)
-> join_view<decltype(detail::range_begin(r)), -> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> { decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep}; return {detail::range_begin(r), detail::range_end(r), sep};
} }
template <typename Char, typename... T> struct tuple_join_view : detail::view { template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple; const Tuple& tuple;
basic_string_view<Char> sep; basic_string_view<Char> sep;
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s) tuple_join_view(const Tuple& t, basic_string_view<Char> s)
: tuple(t), sep{s} {} : tuple(t), sep{s} {}
}; };
@ -718,21 +718,22 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
# define FMT_TUPLE_JOIN_SPECIFIERS 0 # define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif #endif
template <typename Char, typename... T> template <typename Char, typename Tuple>
struct formatter<tuple_join_view<Char, T...>, Char> { struct formatter<tuple_join_view<Char, Tuple>, Char,
enable_if_t<is_tuple_like<Tuple>::value>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>()); return do_parse(ctx, std::tuple_size<Tuple>());
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const tuple_join_view<Char, T...>& value, auto format(const tuple_join_view<Char, Tuple>& value,
FormatContext& ctx) const -> typename FormatContext::iterator { FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx, return do_format(value, ctx, std::tuple_size<Tuple>());
std::integral_constant<size_t, sizeof...(T)>());
} }
private: private:
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_; decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx, FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
std::integral_constant<size_t, 0>) std::integral_constant<size_t, 0>)
@ -746,7 +747,7 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
-> const Char* { -> const Char* {
auto end = ctx.begin(); auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS #if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx); end = std::get<std::tuple_size<Tuple>::value - N>(formatters_).parse(ctx);
if (N > 1) { if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>()); auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1) if (end != end1)
@ -757,18 +758,20 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
} }
template <typename FormatContext> template <typename FormatContext>
auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx, auto do_format(const tuple_join_view<Char, Tuple>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const -> std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator { typename FormatContext::iterator {
return ctx.out(); return ctx.out();
} }
template <typename FormatContext, size_t N> template <typename FormatContext, size_t N>
auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx, auto do_format(const tuple_join_view<Char, Tuple>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const -> std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator { typename FormatContext::iterator {
auto out = std::get<sizeof...(T) - N>(formatters_) using std::get;
.format(std::get<sizeof...(T) - N>(value.tuple), ctx); auto out =
std::get<std::tuple_size<Tuple>::value - N>(formatters_)
.format(get<std::tuple_size<Tuple>::value - N>(value.tuple), ctx);
if (N <= 1) return out; if (N <= 1) return out;
out = detail::copy<Char>(value.sep, out); out = detail::copy<Char>(value.sep, out);
ctx.advance_to(out); ctx.advance_to(out);
@ -825,9 +828,9 @@ FMT_BEGIN_EXPORT
* fmt::print("{}", fmt::join(t, ", ")); * fmt::print("{}", fmt::join(t, ", "));
* // Output: 1, a * // Output: 1, a
*/ */
template <typename... T> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep) FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
-> tuple_join_view<char, T...> { -> tuple_join_view<char, Tuple> {
return {tuple, sep}; return {tuple, sep};
} }

View File

@ -140,7 +140,7 @@ auto join(It begin, Sentinel end, wstring_view sep)
return {begin, end, sep}; return {begin, end, sep};
} }
template <typename Range> template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
auto join(Range&& range, wstring_view sep) auto join(Range&& range, wstring_view sep)
-> join_view<decltype(std::begin(range)), decltype(std::end(range)), -> join_view<decltype(std::begin(range)), decltype(std::end(range)),
wchar_t> { wchar_t> {
@ -153,9 +153,9 @@ auto join(std::initializer_list<T> list, wstring_view sep)
return join(std::begin(list), std::end(list), sep); return join(std::begin(list), std::end(list), sep);
} }
template <typename... T> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep) auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> { -> tuple_join_view<wchar_t, Tuple> {
return {tuple, sep}; return {tuple, sep};
} }

View File

@ -213,7 +213,8 @@ TEST(ranges_test, tuple_parse_calls_element_parse) {
EXPECT_THROW(f.parse(ctx), bad_format); EXPECT_THROW(f.parse(ctx), bad_format);
} }
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT #if defined(FMT_RANGES_TEST_ENABLE_JOIN) || \
defined(FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT)
struct tuple_like { struct tuple_like {
int i; int i;
std::string str; std::string str;
@ -241,7 +242,9 @@ template <size_t N> struct tuple_element<N, tuple_like> {
using type = decltype(std::declval<tuple_like>().get<N>()); using type = decltype(std::declval<tuple_like>().get<N>());
}; };
} // namespace std } // namespace std
#endif
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
TEST(ranges_test, format_struct) { TEST(ranges_test, format_struct) {
auto t = tuple_like{42, "foo"}; auto t = tuple_like{42, "foo"};
EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")"); EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")");
@ -420,6 +423,10 @@ TEST(ranges_test, join_tuple) {
auto t4 = std::tuple<float>(4.0f); auto t4 = std::tuple<float>(4.0f);
EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4"); EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");
// Tuple-like.
auto t5 = tuple_like{42, "foo"};
EXPECT_EQ(fmt::format("{}", fmt::join(t5, ", ")), "42, foo");
# if FMT_TUPLE_JOIN_SPECIFIERS # if FMT_TUPLE_JOIN_SPECIFIERS
// Specs applied to each element. // Specs applied to each element.
auto t5 = std::tuple<int, int, long>(-3, 100, 1); auto t5 = std::tuple<int, int, long>(-3, 100, 1);