Call element parse in tuple parse

This commit is contained in:
Victor Zverovich 2023-02-19 09:24:29 -08:00
parent 507c3042d8
commit 76f520835f
2 changed files with 94 additions and 80 deletions

View File

@ -227,24 +227,44 @@ template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
}; };
template <typename Tuple, typename F, size_t... Is> template <typename Tuple, typename F, size_t... Is>
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) noexcept { FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {
using std::get; using std::get;
// Using free function get<I>(T) now. // Using a free function get<Is>(Tuple) now.
const int unused[] = {0, ((void)f(get<Is>(tup)), 0)...}; const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};
ignore_unused(unused); ignore_unused(unused);
} }
template <typename T> template <typename Tuple, typename F>
FMT_CONSTEXPR auto get_indexes(const T&) FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) {
-> make_index_sequence<std::tuple_size<T>::value> { for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),
return {}; std::forward<Tuple>(t), std::forward<F>(f));
} }
template <typename Tuple, typename F> void for_each(Tuple&& tup, F&& f) { template <typename Tuple1, typename Tuple2, typename F, size_t... Is>
const auto indexes = get_indexes(tup); void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); using std::get;
const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};
ignore_unused(unused);
} }
template <typename Tuple1, typename Tuple2, typename F>
void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {
for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),
std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
std::forward<F>(f));
}
namespace tuple {
// Workaround a bug in MSVC 2019 (v140).
template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get;
template <typename Tuple, typename Char, std::size_t... Is>
auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple
#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
// Older MSVC doesn't get the reference type correctly for arrays. // Older MSVC doesn't get the reference type correctly for arrays.
template <typename R> struct range_reference_type_impl { template <typename R> struct range_reference_type_impl {
@ -268,45 +288,37 @@ using range_reference_type =
template <typename Range> template <typename Range>
using uncvref_type = remove_cvref_t<range_reference_type<Range>>; using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
template <typename Range> template <typename Formatter>
using uncvref_first_type = FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
remove_cvref_t<decltype(std::declval<range_reference_type<Range>>().first)>; -> decltype(f.set_debug_format(set)) {
f.set_debug_format(set);
}
template <typename Formatter>
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
template <typename Range> // These are not generic lambdas for compatibility with C++11.
using uncvref_second_type = remove_cvref_t< template <typename ParseContext> struct parse_empty_specs {
decltype(std::declval<range_reference_type<Range>>().second)>; template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
f.parse(ctx);
detail::maybe_set_debug_format(f, true);
}
ParseContext& ctx;
};
template <typename FormatContext> struct format_tuple_element {
using char_type = typename FormatContext::char_type;
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) { template <typename T>
*out++ = ','; void operator()(const formatter<T, char_type>& f, const T& v) {
*out++ = ' '; if (i > 0)
return out; ctx.advance_to(detail::copy_str<char_type>(separator, ctx.out()));
ctx.advance_to(f.format(v, ctx));
++i;
} }
template <typename Char, typename OutputIt> int i;
auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt { FormatContext& ctx;
return write_escaped_string(out, str); basic_string_view<char_type> separator;
} };
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)>
inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt {
auto sv = std_string_view<Char>(str);
return write_range_entry<Char>(out, basic_string_view<Char>(sv));
}
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(std::is_same<T, Char>::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<typename std::decay<T>::type>::value &&
!std::is_same<T, Char>::value)>
auto write_range_entry(OutputIt out, const T& value) -> OutputIt {
return write<Char>(out, value);
}
} // namespace detail } // namespace detail
@ -325,24 +337,15 @@ struct formatter<Tuple, Char,
enable_if_t<fmt::is_tuple_like<Tuple>::value && enable_if_t<fmt::is_tuple_like<Tuple>::value &&
fmt::is_tuple_formattable<Tuple, Char>::value>> { fmt::is_tuple_formattable<Tuple, Char>::value>> {
private: private:
decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{}; basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ = basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '('>{}; detail::string_literal<Char, '('>{};
basic_string_view<Char> closing_bracket_ = basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ')'>{}; detail::string_literal<Char, ')'>{};
// C++11 generic lambda for format().
template <typename FormatContext> struct format_each {
template <typename T> void operator()(const T& v) {
if (i > 0) out = detail::copy_str<Char>(separator, out);
out = detail::write_range_entry<Char>(out, v);
++i;
}
int i;
typename FormatContext::iterator& out;
basic_string_view<Char> separator;
};
public: public:
FMT_CONSTEXPR formatter() {} FMT_CONSTEXPR formatter() {}
@ -358,16 +361,21 @@ struct formatter<Tuple, Char,
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();
if (it != ctx.end() && *it != '}')
FMT_THROW(format_error("invalid format specifier"));
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
return it;
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const Tuple& value, FormatContext& ctx) const auto format(const Tuple& value, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
auto out = detail::copy_str<Char>(opening_bracket_, ctx.out()); ctx.advance_to(detail::copy_str<Char>(opening_bracket_, ctx.out()));
detail::for_each(value, format_each<FormatContext>{0, out, separator_}); detail::for_each2(
out = detail::copy_str<Char>(closing_bracket_, out); formatters_, value,
return out; detail::format_tuple_element<FormatContext>{0, ctx, separator_});
return detail::copy_str<Char>(closing_bracket_, ctx.out());
} }
}; };
@ -415,7 +423,6 @@ struct is_formattable_delayed
is_formattable<uncvref_type<maybe_const_range<R>>, Char>, is_formattable<uncvref_type<maybe_const_range<R>>, Char>,
has_fallback_formatter<uncvref_type<maybe_const_range<R>>, Char>> {}; has_fallback_formatter<uncvref_type<maybe_const_range<R>>, Char>> {};
#endif #endif
} // namespace detail } // namespace detail
template <typename T, typename Char, typename Enable = void> template <typename T, typename Char, typename Enable = void>
@ -437,19 +444,6 @@ struct range_formatter<
basic_string_view<Char> closing_bracket_ = basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ']'>{}; detail::string_literal<Char, ']'>{};
template <typename U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
-> decltype(u.set_debug_format(set)) {
u.set_debug_format(set);
}
template <typename U>
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: public:
FMT_CONSTEXPR range_formatter() {} FMT_CONSTEXPR range_formatter() {}
@ -482,7 +476,7 @@ struct range_formatter<
custom_specs_ = true; custom_specs_ = true;
++it; ++it;
} else { } else {
maybe_set_debug_format(true); detail::maybe_set_debug_format(underlying_, true);
} }
ctx.advance_to(it); ctx.advance_to(it);

View File

@ -106,6 +106,7 @@ TEST(ranges_test, format_tuple) {
auto t = auto t =
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i'); std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')"); EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()"); EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value)); EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
@ -118,6 +119,25 @@ TEST(ranges_test, format_tuple) {
EXPECT_TRUE((fmt::is_formattable<std::tuple<int, float>>::value)); EXPECT_TRUE((fmt::is_formattable<std::tuple<int, float>>::value));
} }
struct not_default_formattable {};
struct bad_format {};
FMT_BEGIN_NAMESPACE
template <> struct formatter<not_default_formattable> {
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<std::tuple<not_default_formattable>>();
auto ctx = fmt::format_parse_context("");
EXPECT_THROW(f.parse(ctx), bad_format);
}
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT #ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
struct tuple_like { struct tuple_like {
int i; int i;
@ -170,8 +190,8 @@ TEST(ranges_test, path_like) {
EXPECT_FALSE((fmt::is_range<path_like, char>::value)); EXPECT_FALSE((fmt::is_range<path_like, char>::value));
} }
// A range that provides non-const only begin()/end() to test fmt::join handles // A range that provides non-const only begin()/end() to test fmt::join
// that. // handles that.
// //
// Some ranges (e.g. those produced by range-v3's views::filter()) can cache // 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(). // information during iteration so they only provide non-const begin()/end().
@ -234,7 +254,6 @@ TEST(ranges_test, enum_range) {
} }
#if !FMT_MSC_VERSION #if !FMT_MSC_VERSION
TEST(ranges_test, unformattable_range) { TEST(ranges_test, unformattable_range) {
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>, EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
fmt::format_context>::value)); fmt::format_context>::value));
@ -391,6 +410,7 @@ TEST(ranges_test, escape) {
EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}), EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}),
"[['x']]"); "[['x']]");
EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])");
} }
template <typename R> struct fmt_ref_view { template <typename R> struct fmt_ref_view {