From 8358a5e213edc644d823e044c6a6de9d28d21008 Mon Sep 17 00:00:00 2001 From: Jeremy Ong Date: Wed, 25 Sep 2019 19:23:39 -0600 Subject: [PATCH] Provide overload for `fmt::join` that handles `std::tuple`s 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`. --- doc/api.rst | 27 +++++++++++++++ include/fmt/ranges.h | 79 ++++++++++++++++++++++++++++++++++++++++++++ test/ranges-test.cc | 19 +++++++++++ 3 files changed, 125 insertions(+) diff --git a/doc/api.rst b/doc/api.rst index 46231aa246ed..34bc2e376858 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -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 `: the full format API providing compile-time format string checks, output iterator and user-defined type support +* :ref:`fmt/ranges.h `: additional formatting support for ranges + and tuples * :ref:`fmt/chrono.h `: date and time formatting * :ref:`fmt/ostream.h `: ``std::ostream`` support * :ref:`fmt/printf.h `: ``printf`` formatting @@ -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 + + std::tuple 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 + + std::tuple t = {1, 'a'}; + // Prints: "1, a" + fmt::print("{}", fmt::join(t, ", ")); + .. _chrono-api: Date and Time Formatting diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index cf0d41aaa5ee..462c378fbce5 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -283,6 +283,85 @@ struct formatter struct tuple_arg_join : internal::view { + const std::tuple& tuple; + basic_string_view sep; + + tuple_arg_join(const std::tuple& t, basic_string_view s) : tuple{t}, sep{s} {} +}; + +template +struct formatter, Char> +{ + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template + typename FormatContext::iterator format(const tuple_arg_join& value, + FormatContext& ctx) { + return format(value, ctx, internal::make_index_sequence{}); + } + +private: + template + typename FormatContext::iterator format(const tuple_arg_join& value, + FormatContext& ctx, + internal::index_sequence) { + return format_args(value, ctx, std::get(value.tuple)...); + } + + template + typename FormatContext::iterator format_args(const tuple_arg_join&, + 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::iterator format_args(const tuple_arg_join& value, + FormatContext& ctx, + const Arg& arg, + const Args&... args) { + + using base = formatter::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 t = {1, 'a'}; + fmt::print("{}", fmt::join(t, ", ")); + // Output: "1, a" + \endrst + */ +template +FMT_CONSTEXPR tuple_arg_join join(const std::tuple& tuple, string_view sep) { + return {tuple, sep}; +} + +template +FMT_CONSTEXPR tuple_arg_join join(const std::tuple& tuple, wstring_view sep) { + return {tuple, sep}; +} + FMT_END_NAMESPACE #endif // FMT_RANGES_H_ diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 7d4b6d2442fe..56c8aa9875d1 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -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 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 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 t4{4.0f}; + EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/"))); +} + struct my_struct { int32_t i; std::string str; // can throw