diff --git a/doc/api.rst b/doc/api.rst index 46231aa2..247d0ee7 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 cf0d41aa..e031038a 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -283,6 +283,82 @@ 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...); + } + 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 7d4b6d24..56c8aa98 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