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

#1322 tuple join #1330

Closed
wants to merge 1 commit into from
Closed
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
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