mirror of
https://github.com/fmtlib/fmt.git
synced 2025-08-01 19:54:46 +02:00
Supporting nested format specs for ranges. (#2673)
* Supporting nested format specs for ranges. * I dedicate this commit to Eric Niebler. * clang-format * PR comments. * throw -> FMT_THROW * Need to map every element too. * Clarifying uncvref_type * Trying to add a workaround for MSVC.
This commit is contained in:
@@ -221,9 +221,28 @@ template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
|||||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if FMT_MSC_VER
|
||||||
|
// older MSVC doesn't get the reference type correctly for arrays
|
||||||
|
template <typename R> struct range_reference_type_impl {
|
||||||
|
using type = decltype(*detail::range_begin(std::declval<R&>()));
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
|
||||||
|
using type = T&;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using range_reference_type = typename range_reference_type_impl<T>::type;
|
||||||
|
#else
|
||||||
template <typename Range>
|
template <typename Range>
|
||||||
using value_type =
|
using range_reference_type =
|
||||||
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
|
decltype(*detail::range_begin(std::declval<Range&>()));
|
||||||
|
#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 <typename Range>
|
||||||
|
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
|
||||||
|
|
||||||
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
|
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
|
||||||
*out++ = ',';
|
*out++ = ',';
|
||||||
@@ -582,35 +601,79 @@ template <typename T, typename Char> struct is_range {
|
|||||||
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, typename Char>
|
namespace detail {
|
||||||
|
template <typename Context, typename Element> struct range_mapper {
|
||||||
|
using mapper = arg_mapper<Context>;
|
||||||
|
|
||||||
|
template <typename T,
|
||||||
|
FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
|
||||||
|
static auto map(T&& value) -> T&& {
|
||||||
|
return static_cast<T&&>(value);
|
||||||
|
}
|
||||||
|
template <typename T,
|
||||||
|
FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
|
||||||
|
static auto map(T&& value)
|
||||||
|
-> decltype(mapper().map(static_cast<T&&>(value))) {
|
||||||
|
return mapper().map(static_cast<T&&>(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char, typename Element>
|
||||||
|
using range_formatter_type =
|
||||||
|
conditional_t<is_formattable<Element, Char>::value,
|
||||||
|
formatter<remove_cvref_t<decltype(
|
||||||
|
range_mapper<buffer_context<Char>, Element>{}
|
||||||
|
.map(std::declval<Element>()))>,
|
||||||
|
Char>,
|
||||||
|
fallback_formatter<Element, Char>>;
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename R, typename Char>
|
||||||
struct formatter<
|
struct formatter<
|
||||||
T, Char,
|
R, Char,
|
||||||
enable_if_t<
|
enable_if_t<fmt::is_range<R, Char>::value
|
||||||
fmt::is_range<T, Char>::value
|
|
||||||
// Workaround a bug in MSVC 2019 and earlier.
|
// Workaround a bug in MSVC 2019 and earlier.
|
||||||
#if !FMT_MSC_VER
|
#if !FMT_MSC_VER
|
||||||
&& (is_formattable<detail::value_type<T>, Char>::value ||
|
&& (is_formattable<detail::uncvref_type<R>, Char>::value ||
|
||||||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
detail::has_fallback_formatter<detail::uncvref_type<R>,
|
||||||
|
Char>::value)
|
||||||
#endif
|
#endif
|
||||||
>> {
|
>> {
|
||||||
|
|
||||||
|
using formatter_type =
|
||||||
|
detail::range_formatter_type<Char, detail::uncvref_type<R>>;
|
||||||
|
formatter_type underlying_;
|
||||||
|
bool custom_specs_ = false;
|
||||||
|
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
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 <
|
template <
|
||||||
typename FormatContext, typename U,
|
typename FormatContext, typename U,
|
||||||
FMT_ENABLE_IF(
|
FMT_ENABLE_IF(
|
||||||
std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
|
std::is_same<U, conditional_t<detail::has_const_begin_end<R>::value,
|
||||||
const T, T>>::value)>
|
const R, R>>::value)>
|
||||||
auto format(U& range, FormatContext& ctx) -> decltype(ctx.out()) {
|
auto format(U& range, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||||
#ifdef FMT_DEPRECATED_BRACED_RANGES
|
#ifdef FMT_DEPRECATED_BRACED_RANGES
|
||||||
Char prefix = '{';
|
Char prefix = '{';
|
||||||
Char postfix = '}';
|
Char postfix = '}';
|
||||||
#else
|
#else
|
||||||
Char prefix = detail::is_set<T>::value ? '{' : '[';
|
Char prefix = detail::is_set<R>::value ? '{' : '[';
|
||||||
Char postfix = detail::is_set<T>::value ? '}' : ']';
|
Char postfix = detail::is_set<R>::value ? '}' : ']';
|
||||||
#endif
|
#endif
|
||||||
|
detail::range_mapper<buffer_context<Char>, detail::uncvref_type<R>> mapper;
|
||||||
auto out = ctx.out();
|
auto out = ctx.out();
|
||||||
*out++ = prefix;
|
*out++ = prefix;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@@ -618,7 +681,12 @@ struct formatter<
|
|||||||
auto end = std::end(range);
|
auto end = std::end(range);
|
||||||
for (; it != end; ++it) {
|
for (; it != end; ++it) {
|
||||||
if (i > 0) out = detail::write_delimiter(out);
|
if (i > 0) out = detail::write_delimiter(out);
|
||||||
out = detail::write_range_entry<Char>(out, *it);
|
if (custom_specs_) {
|
||||||
|
ctx.advance_to(out);
|
||||||
|
out = underlying_.format(mapper.map(*it), ctx);
|
||||||
|
} else {
|
||||||
|
out = detail::write_range_entry<Char>(out, *it);
|
||||||
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
*out++ = postfix;
|
*out++ = postfix;
|
||||||
@@ -629,14 +697,14 @@ struct formatter<
|
|||||||
template <typename T, typename Char>
|
template <typename T, typename Char>
|
||||||
struct formatter<
|
struct formatter<
|
||||||
T, Char,
|
T, Char,
|
||||||
enable_if_t<
|
enable_if_t<detail::is_map<T>::value
|
||||||
detail::is_map<T>::value
|
|
||||||
// Workaround a bug in MSVC 2019 and earlier.
|
// Workaround a bug in MSVC 2019 and earlier.
|
||||||
#if !FMT_MSC_VER
|
#if !FMT_MSC_VER
|
||||||
&& (is_formattable<detail::value_type<T>, Char>::value ||
|
&& (is_formattable<detail::uncvref_type<T>, Char>::value ||
|
||||||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
detail::has_fallback_formatter<detail::uncvref_type<T>,
|
||||||
|
Char>::value)
|
||||||
#endif
|
#endif
|
||||||
>> {
|
>> {
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
|
@@ -46,11 +46,13 @@ TEST(ranges_test, format_array_of_literals) {
|
|||||||
TEST(ranges_test, format_vector) {
|
TEST(ranges_test, format_vector) {
|
||||||
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
|
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
|
||||||
EXPECT_EQ(fmt::format("{}", v), "[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) {
|
TEST(ranges_test, format_vector2) {
|
||||||
auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}};
|
auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}};
|
||||||
EXPECT_EQ(fmt::format("{}", v), "[[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) {
|
TEST(ranges_test, format_map) {
|
||||||
@@ -296,6 +298,7 @@ static_assert(std::input_iterator<cpp20_only_range::iterator>);
|
|||||||
TEST(ranges_test, join_sentinel) {
|
TEST(ranges_test, join_sentinel) {
|
||||||
auto hello = zstring{"hello"};
|
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("{::}", hello), "[h, e, l, l, o]");
|
||||||
EXPECT_EQ(fmt::format("{}", fmt::join(hello, "_")), "h_e_l_l_o");
|
EXPECT_EQ(fmt::format("{}", fmt::join(hello, "_")), "h_e_l_l_o");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user