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 80fb380
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 0 deletions.
27 changes: 27 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The {fmt} library API consists of the following parts:
facilities and a lightweight subset of formatting functions
* :ref:`fmt/format.h <format-api>`: the full format API providing compile-time
format string checks, output iterator and user-defined type support
* :ref:`fmt/ranges.h <ranges-api>`: additional formatting support for ranges
and tuples
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
Expand Down Expand Up @@ -317,6 +319,31 @@ custom argument formatter class::
.. doxygenclass:: fmt::arg_formatter
:members:

.. _ranges-api:

Ranges and Tuple Formatting
===========================

The library also supports convenient formatting of ranges and tuples::

#include <fmt/ranges.h>

std::tuple<char, int, float> t{'a', 1, 2.0f};
// Prints a 1 2.0
fmt::print("{}", t);


NOTE: currently, the overload of `fmt::join` for iterables exists in the main
`format.h` header, but expect this to change in the future.

Using `fmt::join`, you can separate tuple elements with a custom separator::

#include <fmt/ranges.h>

std::tuple<int, char> t = {1, 'a'};
// Prints: "1, a"
fmt::print("{}", fmt::join(t, ", "));

.. _chrono-api:

Date and Time Formatting
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...>&,
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>
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple, string_view sep) {
return {tuple, sep};
}

template <typename... T>
FMT_CONSTEXPR 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 80fb380

Please sign in to comment.