diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index eb9fb8a9..dcdbb899 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -221,9 +221,28 @@ template void for_each(Tuple&& tup, F&& f) { for_each(indexes, std::forward(tup), std::forward(f)); } +#if FMT_MSC_VER +// older MSVC doesn't get the reference type correctly for arrays +template struct range_reference_type_impl { + using type = decltype(*detail::range_begin(std::declval())); +}; + +template struct range_reference_type_impl { + using type = T&; +}; + +template +using range_reference_type = typename range_reference_type_impl::type; +#else template -using value_type = - remove_cvref_t()))>; +using range_reference_type = + decltype(*detail::range_begin(std::declval())); +#endif + +// We don't use the Range's value_type for anything, but we do need the Range's +// reference type, with cv-ref stripped +template +using uncvref_type = remove_cvref_t>; template OutputIt write_delimiter(OutputIt out) { *out++ = ','; @@ -582,35 +601,79 @@ template struct is_range { !std::is_constructible, T>::value; }; -template +namespace detail { +template struct range_mapper { + using mapper = arg_mapper; + + template , Context>::value)> + static auto map(T&& value) -> T&& { + return static_cast(value); + } + template , Context>::value)> + static auto map(T&& value) + -> decltype(mapper().map(static_cast(value))) { + return mapper().map(static_cast(value)); + } +}; + +template +using range_formatter_type = + conditional_t::value, + formatter, Element>{} + .map(std::declval()))>, + Char>, + fallback_formatter>; +} // namespace detail + +template struct formatter< - T, Char, - enable_if_t< - fmt::is_range::value + R, Char, + enable_if_t::value // Workaround a bug in MSVC 2019 and earlier. #if !FMT_MSC_VER - && (is_formattable, Char>::value || - detail::has_fallback_formatter, Char>::value) + && (is_formattable, Char>::value || + detail::has_fallback_formatter, + Char>::value) #endif - >> { + >> { + + using formatter_type = + detail::range_formatter_type>; + formatter_type underlying_; + bool custom_specs_ = false; + template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); + auto it = ctx.begin(); + auto end = ctx.end(); + if (it == end || *it == '}') return it; + + if (*it != ':') + FMT_THROW(format_error("no top-level range formatters supported")); + + custom_specs_ = true; + ++it; + ctx.advance_to(it); + return underlying_.parse(ctx); } template < typename FormatContext, typename U, FMT_ENABLE_IF( - std::is_same::value, - const T, T>>::value)> + std::is_same::value, + const R, R>>::value)> auto format(U& range, FormatContext& ctx) -> decltype(ctx.out()) { #ifdef FMT_DEPRECATED_BRACED_RANGES Char prefix = '{'; Char postfix = '}'; #else - Char prefix = detail::is_set::value ? '{' : '['; - Char postfix = detail::is_set::value ? '}' : ']'; + Char prefix = detail::is_set::value ? '{' : '['; + Char postfix = detail::is_set::value ? '}' : ']'; #endif + detail::range_mapper, detail::uncvref_type> mapper; auto out = ctx.out(); *out++ = prefix; int i = 0; @@ -618,7 +681,12 @@ struct formatter< auto end = std::end(range); for (; it != end; ++it) { if (i > 0) out = detail::write_delimiter(out); - out = detail::write_range_entry(out, *it); + if (custom_specs_) { + ctx.advance_to(out); + out = underlying_.format(mapper.map(*it), ctx); + } else { + out = detail::write_range_entry(out, *it); + } ++i; } *out++ = postfix; @@ -629,14 +697,14 @@ struct formatter< template struct formatter< T, Char, - enable_if_t< - detail::is_map::value + enable_if_t::value // Workaround a bug in MSVC 2019 and earlier. #if !FMT_MSC_VER - && (is_formattable, Char>::value || - detail::has_fallback_formatter, Char>::value) + && (is_formattable, Char>::value || + detail::has_fallback_formatter, + Char>::value) #endif - >> { + >> { template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { return ctx.begin(); diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 63cb8c8b..97e84cd7 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -46,11 +46,13 @@ TEST(ranges_test, format_array_of_literals) { TEST(ranges_test, format_vector) { auto v = std::vector{1, 2, 3, 5, 7, 11}; EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]"); + EXPECT_EQ(fmt::format("{::#x}", v), "[0x1, 0x2, 0x3, 0x5, 0x7, 0xb]"); } TEST(ranges_test, format_vector2) { auto v = std::vector>{{1, 2}, {3, 5}, {7, 11}}; EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]"); + EXPECT_EQ(fmt::format("{:::#x}", v), "[[0x1, 0x2], [0x3, 0x5], [0x7, 0xb]]"); } TEST(ranges_test, format_map) { @@ -296,6 +298,7 @@ static_assert(std::input_iterator); TEST(ranges_test, join_sentinel) { auto hello = zstring{"hello"}; EXPECT_EQ(fmt::format("{}", hello), "['h', 'e', 'l', 'l', 'o']"); + EXPECT_EQ(fmt::format("{::}", hello), "[h, e, l, l, o]"); EXPECT_EQ(fmt::format("{}", fmt::join(hello, "_")), "h_e_l_l_o"); }