Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 22 additions & 19 deletions include/fmt/ranges.h
Original file line number Diff line number Diff line change
Expand Up @@ -696,18 +696,18 @@ auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
* fmt::print("{:02}", fmt::join(v, ", "));
* // 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)
-> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep};
}

template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple;
template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
const Tuple& tuple;
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} {}
};

Expand All @@ -718,21 +718,22 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif

template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename Char, typename Tuple>
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* {
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
return do_parse(ctx, std::tuple_size<Tuple>());
}

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 {
return do_format(value, ctx,
std::integral_constant<size_t, sizeof...(T)>());
return do_format(value, ctx, std::tuple_size<Tuple>());
}

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,
std::integral_constant<size_t, 0>)
Expand All @@ -746,7 +747,7 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
-> const Char* {
auto end = ctx.begin();
#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) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1)
Expand All @@ -757,18 +758,20 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
}

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 ->
typename FormatContext::iterator {
return ctx.out();
}

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 ->
typename FormatContext::iterator {
auto out = std::get<sizeof...(T) - N>(formatters_)
.format(std::get<sizeof...(T) - N>(value.tuple), ctx);
using std::get;
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;
out = detail::copy<Char>(value.sep, out);
ctx.advance_to(out);
Expand Down Expand Up @@ -825,9 +828,9 @@ FMT_BEGIN_EXPORT
* fmt::print("{}", fmt::join(t, ", "));
* // Output: 1, a
*/
template <typename... T>
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
-> tuple_join_view<char, T...> {
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
-> tuple_join_view<char, Tuple> {
return {tuple, sep};
}

Expand Down
8 changes: 4 additions & 4 deletions include/fmt/xchar.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ auto join(It begin, Sentinel end, wstring_view 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)
-> join_view<decltype(std::begin(range)), decltype(std::end(range)),
wchar_t> {
Expand All @@ -153,9 +153,9 @@ auto join(std::initializer_list<T> list, wstring_view sep)
return join(std::begin(list), std::end(list), sep);
}

template <typename... T>
auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> {
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, Tuple> {
return {tuple, sep};
}

Expand Down
9 changes: 8 additions & 1 deletion test/ranges-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ TEST(ranges_test, tuple_parse_calls_element_parse) {
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)
vitaut marked this conversation as resolved.
Show resolved Hide resolved
struct tuple_like {
int i;
std::string str;
Expand Down Expand Up @@ -241,7 +242,9 @@ template <size_t N> struct tuple_element<N, tuple_like> {
using type = decltype(std::declval<tuple_like>().get<N>());
};
} // namespace std
#endif

#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
TEST(ranges_test, format_struct) {
auto t = tuple_like{42, "foo"};
EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")");
Expand Down Expand Up @@ -420,6 +423,10 @@ TEST(ranges_test, join_tuple) {
auto t4 = std::tuple<float>(4.0f);
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
// Specs applied to each element.
auto t5 = std::tuple<int, int, long>(-3, 100, 1);
Expand Down