Skip to content

Commit

Permalink
Provide overload for fmt::join that handles std::tuples
Browse files Browse the repository at this point in the history
Address enhancement request #1322.

The overload is provided in `ranges` (original `fmt::join` exists
currently in `format.h` for historical reasons.

Tests for prvalue and lvalue tuple arguments as well as the empty
tuple are provided in `ranges-test.cc`.
  • Loading branch information
jeremyong committed Sep 26, 2019
1 parent 4b8f8fa commit 151ee37
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 0 deletions.
2 changes: 2 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ Utilities

.. doxygenfunction:: fmt::join(const Range&, string_view)

.. doxygenfunction:: fmt::join(const std::tuple<T...>&, string_view)

.. doxygenfunction:: fmt::join(It, It, string_view)

.. doxygenclass:: fmt::basic_memory_buffer
Expand Down
79 changes: 79 additions & 0 deletions include/fmt/ranges.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,85 @@ struct formatter<RangeT, Char,
}
};

template <typename Char, typename... T> struct tuple_arg_join : internal::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep;

tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s) : tuple{t}, sep{s} {}
};

template <typename Char, typename... T>
struct formatter<tuple_arg_join<Char, T...>, Char>
{
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}

template <typename FormatContext>
typename FormatContext::iterator format(const tuple_arg_join<Char, T...>& value,
FormatContext& ctx) {
return format(value, ctx, internal::make_index_sequence<sizeof...(T)>{});
}

private:
template <typename FormatContext, size_t... N>
typename FormatContext::iterator format(const tuple_arg_join<Char, T...>& value,
FormatContext& ctx,
internal::index_sequence<N...>) {
return format_args(value, ctx, std::get<N>(value.tuple)...);
}

template <typename FormatContext>
typename FormatContext::iterator format_args(const tuple_arg_join<Char, T...>& value,
FormatContext& ctx) {
// NOTE: for compilers that support C++17, this empty function instantiation can be replaced
// with a constexpr branch in the variadic overload
return ctx.out();
}

template <typename FormatContext, typename Arg, typename... Args>
typename FormatContext::iterator format_args(const tuple_arg_join<Char, T...>& value,
FormatContext& ctx,
const Arg& arg,
const Args&... args) {

using base = formatter<typename std::decay<Arg>::type, Char>;
auto out = ctx.out();
out = base{}.format(arg, ctx);
if (sizeof...(Args) > 0) {
out = std::copy(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out);
return format_args(value, ctx, args...);
}
else
{
return out;
}
}
};

/**
\rst
Returns an object that formats `tuple` with elements separated by `sep`.
**Example**::
std::tuple<int, char> t = {1, 'a'};
fmt::print("{}", fmt::join(t, ", "));
// Output: "1, a"
\endrst
*/
template <typename... T>
tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple, string_view sep) {
return {tuple, sep};
}

template <typename... T>
tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple, wstring_view sep) {
return {tuple, sep};
}

FMT_END_NAMESPACE

#endif // FMT_RANGES_H_
19 changes: 19 additions & 0 deletions test/ranges-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@ TEST(RangesTest, FormatTuple) {
EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", tu1));
}

TEST(RangesTest, JoinTuple) {
// Value tuple args
std::tuple<char, int, float> t1 = std::make_tuple('a', 1, 2.0f);
EXPECT_EQ("(a, 1, 2.0)", fmt::format("({})", fmt::join(t1, ", ")));

// Testing lvalue tuple args
int x = 4;
std::tuple<char, int&> t2{'b', x};
EXPECT_EQ("b + 4", fmt::format("{}", fmt::join(t2, " + ")));

// Empty tuple
std::tuple<> t3;
EXPECT_EQ("", fmt::format("{}", fmt::join(t3, "|")));

// Single element tuple
std::tuple<float> t4{4.0f};
EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/")));
}

struct my_struct {
int32_t i;
std::string str; // can throw
Expand Down

0 comments on commit 151ee37

Please sign in to comment.