diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 711424c8..00bea734 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -227,24 +227,44 @@ template class is_tuple_formattable_ { }; template -void for_each(index_sequence, Tuple&& tup, F&& f) noexcept { +FMT_CONSTEXPR void for_each(index_sequence, Tuple&& t, F&& f) { using std::get; - // Using free function get(T) now. - const int unused[] = {0, ((void)f(get(tup)), 0)...}; + // Using a free function get(Tuple) now. + const int unused[] = {0, ((void)f(get(t)), 0)...}; ignore_unused(unused); } -template -FMT_CONSTEXPR auto get_indexes(const T&) - -> make_index_sequence::value> { - return {}; +template +FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) { + for_each(tuple_index_sequence>(), + std::forward(t), std::forward(f)); } -template void for_each(Tuple&& tup, F&& f) { - const auto indexes = get_indexes(tup); - for_each(indexes, std::forward(tup), std::forward(f)); +template +void for_each2(index_sequence, Tuple1&& t1, Tuple2&& t2, F&& f) { + using std::get; + const int unused[] = {0, ((void)f(get(t1), get(t2)), 0)...}; + ignore_unused(unused); } +template +void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) { + for_each2(tuple_index_sequence>(), + std::forward(t1), std::forward(t2), + std::forward(f)); +} + +namespace tuple { +// Workaround a bug in MSVC 2019 (v140). +template +using result_t = std::tuple, Char>...>; + +using std::get; +template +auto get_formatters(index_sequence) + -> result_t(std::declval()))...>; +} // namespace tuple + #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 // Older MSVC doesn't get the reference type correctly for arrays. template struct range_reference_type_impl { @@ -268,45 +288,37 @@ using range_reference_type = template using uncvref_type = remove_cvref_t>; -template -using uncvref_first_type = - remove_cvref_t>().first)>; - -template -using uncvref_second_type = remove_cvref_t< - decltype(std::declval>().second)>; - -template OutputIt write_delimiter(OutputIt out) { - *out++ = ','; - *out++ = ' '; - return out; +template +FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) + -> decltype(f.set_debug_format(set)) { + f.set_debug_format(set); } +template +FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} -template -auto write_range_entry(OutputIt out, basic_string_view str) -> OutputIt { - return write_escaped_string(out, str); -} +// These are not generic lambdas for compatibility with C++11. +template struct parse_empty_specs { + template FMT_CONSTEXPR void operator()(Formatter& f) { + f.parse(ctx); + detail::maybe_set_debug_format(f, true); + } + ParseContext& ctx; +}; +template struct format_tuple_element { + using char_type = typename FormatContext::char_type; -template >::value)> -inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt { - auto sv = std_string_view(str); - return write_range_entry(out, basic_string_view(sv)); -} + template + void operator()(const formatter& f, const T& v) { + if (i > 0) + ctx.advance_to(detail::copy_str(separator, ctx.out())); + ctx.advance_to(f.format(v, ctx)); + ++i; + } -template ::value)> -auto write_range_entry(OutputIt out, T value) -> OutputIt { - return write_escaped_char(out, value); -} - -template < - typename Char, typename OutputIt, typename T, - FMT_ENABLE_IF(!is_std_string_like::type>::value && - !std::is_same::value)> -auto write_range_entry(OutputIt out, const T& value) -> OutputIt { - return write(out, value); -} + int i; + FormatContext& ctx; + basic_string_view separator; +}; } // namespace detail @@ -325,24 +337,15 @@ struct formatter::value && fmt::is_tuple_formattable::value>> { private: + decltype(detail::tuple::get_formatters( + detail::tuple_index_sequence())) formatters_; + basic_string_view separator_ = detail::string_literal{}; basic_string_view opening_bracket_ = detail::string_literal{}; basic_string_view closing_bracket_ = detail::string_literal{}; - // C++11 generic lambda for format(). - template struct format_each { - template void operator()(const T& v) { - if (i > 0) out = detail::copy_str(separator, out); - out = detail::write_range_entry(out, v); - ++i; - } - int i; - typename FormatContext::iterator& out; - basic_string_view separator; - }; - public: FMT_CONSTEXPR formatter() {} @@ -358,16 +361,21 @@ struct formatter FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); + auto it = ctx.begin(); + if (it != ctx.end() && *it != '}') + FMT_THROW(format_error("invalid format specifier")); + detail::for_each(formatters_, detail::parse_empty_specs{ctx}); + return it; } template auto format(const Tuple& value, FormatContext& ctx) const -> decltype(ctx.out()) { - auto out = detail::copy_str(opening_bracket_, ctx.out()); - detail::for_each(value, format_each{0, out, separator_}); - out = detail::copy_str(closing_bracket_, out); - return out; + ctx.advance_to(detail::copy_str(opening_bracket_, ctx.out())); + detail::for_each2( + formatters_, value, + detail::format_tuple_element{0, ctx, separator_}); + return detail::copy_str(closing_bracket_, ctx.out()); } }; @@ -415,7 +423,6 @@ struct is_formattable_delayed is_formattable>, Char>, has_fallback_formatter>, Char>> {}; #endif - } // namespace detail template @@ -437,19 +444,6 @@ struct range_formatter< basic_string_view closing_bracket_ = detail::string_literal{}; - template - FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set) - -> decltype(u.set_debug_format(set)) { - u.set_debug_format(set); - } - - template - FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {} - - FMT_CONSTEXPR void maybe_set_debug_format(bool set) { - maybe_set_debug_format(underlying_, set); - } - public: FMT_CONSTEXPR range_formatter() {} @@ -482,7 +476,7 @@ struct range_formatter< custom_specs_ = true; ++it; } else { - maybe_set_debug_format(true); + detail::maybe_set_debug_format(underlying_, true); } ctx.advance_to(it); diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 29ec401a..e4a2d0e3 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -106,6 +106,7 @@ TEST(ranges_test, format_tuple) { auto t = std::tuple(42, 1.5f, "this is tuple", 'i'); EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')"); + EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()"); EXPECT_TRUE((fmt::is_formattable>::value)); @@ -118,6 +119,25 @@ TEST(ranges_test, format_tuple) { EXPECT_TRUE((fmt::is_formattable>::value)); } +struct not_default_formattable {}; +struct bad_format {}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter { + auto parse(format_parse_context&) -> const char* { throw bad_format(); } + auto format(not_default_formattable, format_context& ctx) + -> format_context::iterator { + return ctx.out(); + } +}; +FMT_END_NAMESPACE + +TEST(ranges_test, tuple_parse_calls_element_parse) { + auto f = fmt::formatter>(); + auto ctx = fmt::format_parse_context(""); + EXPECT_THROW(f.parse(ctx), bad_format); +} + #ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT struct tuple_like { int i; @@ -170,8 +190,8 @@ TEST(ranges_test, path_like) { EXPECT_FALSE((fmt::is_range::value)); } -// A range that provides non-const only begin()/end() to test fmt::join handles -// that. +// A range that provides non-const only begin()/end() to test fmt::join +// handles that. // // Some ranges (e.g. those produced by range-v3's views::filter()) can cache // information during iteration so they only provide non-const begin()/end(). @@ -234,7 +254,6 @@ TEST(ranges_test, enum_range) { } #if !FMT_MSC_VERSION - TEST(ranges_test, unformattable_range) { EXPECT_FALSE((fmt::has_formatter, fmt::format_context>::value)); @@ -391,6 +410,7 @@ TEST(ranges_test, escape) { EXPECT_EQ(fmt::format("{}", std::vector>{{'x'}}), "[['x']]"); + EXPECT_EQ(fmt::format("{}", std::tuple>{{'x'}}), "(['x'])"); } template struct fmt_ref_view {