From f4ca065cfbdb6199f90c9bad7bcd89454930a4db Mon Sep 17 00:00:00 2001 From: kitegi Date: Sat, 24 Oct 2020 11:25:29 +0200 Subject: [PATCH] Range support --- include/fmt/core.h | 11 ++++- include/fmt/ranges.h | 106 +++++++++++++++++++++++++++++++++++++++---- test/ranges-test.cc | 52 +++++++++++++++++++++ 3 files changed, 160 insertions(+), 9 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 4278f0e1..21cdadb5 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1198,7 +1198,11 @@ template struct arg_mapper { FMT_CONSTEXPR const void* map(void* val) { return val; } FMT_CONSTEXPR const void* map(const void* val) { return val; } FMT_CONSTEXPR const void* map(std::nullptr_t val) { return val; } - template FMT_CONSTEXPR int map(const T*) { + + // We use SFINAE instead of a const T* parameter to avoid conflicting with + // the C array overload. + template + FMT_CONSTEXPR auto map(T) -> enable_if_t::value, int> { // Formatting of arbitrary pointers is disallowed. If you want to output // a pointer cast it to "void *" or "const void *". In particular, this // forbids formatting of "[const] volatile char *" which is printed as bool @@ -1207,6 +1211,11 @@ template struct arg_mapper { return 0; } + template + FMT_CONSTEXPR auto map(const T (&values)[N]) -> const T (&)[N] { + return values; + } + template ::value && !has_formatter::value && diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index b603d637..650d38c9 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -95,12 +95,100 @@ template struct conditional_helper {}; template struct is_range_ : std::false_type {}; #if !FMT_MSC_VER || FMT_MSC_VER > 1800 + +# define FMT_DECLTYPE_RETURN(val) \ + ->decltype(val) { return val; } \ + static_assert( \ + true, "") // This makes it so that a semicolon is required after the + // macro, which helps clang-format handle the formatting. + +// C array overload +template +auto range_begin(const T (&arr)[N]) -> const T* { + return arr; +} +template +auto range_end(const T (&arr)[N]) -> const T* { + return arr + N; +} + +template +struct has_member_fn_begin_end_t : std::false_type {}; + template -struct is_range_< - T, conditional_t().begin()), - decltype(std::declval().end())>, - void>> : std::true_type {}; +struct has_member_fn_begin_end_t().begin()), + decltype(std::declval().end())>> + : std::true_type {}; + +// Member function overload +template +auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast(rng).begin()); +template +auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast(rng).end()); + +// ADL overload. Only participates in overload resolution if member functions +// are not found. +template +auto range_begin(T&& rng) + -> enable_if_t::value, + decltype(begin(static_cast(rng)))> { + return begin(static_cast(rng)); +} +template +auto range_end(T&& rng) -> enable_if_t::value, + decltype(end(static_cast(rng)))> { + return end(static_cast(rng)); +} + +template +struct has_const_begin_end : std::false_type {}; +template +struct has_mutable_begin_end : std::false_type {}; + +template +struct has_const_begin_end< + T, void_t&>())), + decltype(detail::range_begin( + std::declval&>()))>> + : std::true_type {}; + +template +struct has_mutable_begin_end< + T, void_t())), + decltype(detail::range_begin(std::declval())), + enable_if_t::value>>> + : std::true_type {}; + +template +struct is_range_ + : std::integral_constant::value || + has_mutable_begin_end::value)> {}; + +template struct range_to_view; +template +struct range_to_view::value>> { + struct view_t { + const T* m_range_ptr; + + auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr)); + auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr)); + }; + static auto view(const T& range) -> view_t { return {&range}; } +}; + +template +struct range_to_view::value && + has_mutable_begin_end::value>> { + struct view_t { + T m_range_copy; + + auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy)); + auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy)); + }; + static auto view(const T& range) -> view_t { return {range}; } +}; +# undef FMT_DECLTYPE_RETURN #endif /// tuple_size and tuple_element check. @@ -158,7 +246,8 @@ template void for_each(Tuple&& tup, F&& f) { } template -using value_type = remove_cvref_t().begin())>; +using value_type = + remove_cvref_t()))>; template ::type>::value)> @@ -268,8 +357,9 @@ struct formatter< typename FormatContext::iterator format(const T& values, FormatContext& ctx) { auto out = detail::copy(formatting.prefix, ctx.out()); size_t i = 0; - auto it = values.begin(); - auto end = values.end(); + auto view = detail::range_to_view::view(values); + auto it = view.begin(); + auto end = view.end(); for (; it != end; ++it) { if (i > 0) { if (formatting.add_prepostfix_space) *out++ = ' '; diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 63f9e6e6..c05e5fcb 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -22,6 +22,18 @@ # include # include +TEST(RangesTest, FormatArray) { + int32_t ia[] = {1, 2, 3, 5, 7, 11}; + auto iaf = fmt::format("{}", ia); + EXPECT_EQ("{1, 2, 3, 5, 7, 11}", iaf); +} + +TEST(RangesTest, Format2dArray) { + int32_t ia[][2] = {{1, 2}, {3, 5}, {7, 11}}; + auto iaf = fmt::format("{}", ia); + EXPECT_EQ("{{1, 2}, {3, 5}, {7, 11}}", iaf); +} + TEST(RangesTest, FormatVector) { std::vector iv{1, 2, 3, 5, 7, 11}; auto ivf = fmt::format("{}", iv); @@ -177,7 +189,30 @@ template class non_const_only_range { const_iterator end() { return vec.end(); } }; +template class noncopyable_range { + private: + std::vector vec; + + public: + using const_iterator = typename ::std::vector::const_iterator; + + template + explicit noncopyable_range(Args&&... args) + : vec(::std::forward(args)...) {} + + noncopyable_range(noncopyable_range const&) = delete; + noncopyable_range(noncopyable_range&) = delete; + + const_iterator begin() const { return vec.begin(); } + const_iterator end() const { return vec.end(); } +}; + TEST(RangesTest, JoinRange) { + noncopyable_range w(3u, 0); + EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(w, ","))); + EXPECT_EQ("0,0,0", + fmt::format("{}", fmt::join(noncopyable_range(3u, 0), ","))); + non_const_only_range x(3u, 0); EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(x, ","))); EXPECT_EQ( @@ -193,6 +228,23 @@ TEST(RangesTest, JoinRange) { EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(z, ","))); } +TEST(RangesTest, Range) { + noncopyable_range w(3u, 0); + EXPECT_EQ("{0, 0, 0}", fmt::format("{}", w)); + EXPECT_EQ("{0, 0, 0}", fmt::format("{}", noncopyable_range(3u, 0))); + + non_const_only_range x(3u, 0); + EXPECT_EQ("{0, 0, 0}", fmt::format("{}", x)); + EXPECT_EQ("{0, 0, 0}", fmt::format("{}", non_const_only_range(3u, 0))); + + std::vector y(3u, 0); + EXPECT_EQ("{0, 0, 0}", fmt::format("{}", y)); + EXPECT_EQ("{0, 0, 0}", fmt::format("{}", std::vector(3u, 0))); + + const std::vector z(3u, 0); + EXPECT_EQ("{0, 0, 0}", fmt::format("{}", z)); +} + #if !FMT_MSC_VER || FMT_MSC_VER >= 1927 struct unformattable {};