From 670f8c074cabaebf8636d84bb8ceaaaa0056c7bb Mon Sep 17 00:00:00 2001 From: Jerry Ma Date: Tue, 28 Sep 2021 23:36:34 -0400 Subject: [PATCH] Add concepts supoort for ranges formatter; Allow customize prefix, delimiter and postfix. --- include/fmt/core.h | 7 ++ include/fmt/ranges.h | 135 +++++++++++++++++++++------ test/CMakeLists.txt | 3 + test/ranges-custom-formatter-test.cc | 64 +++++++++++++ 4 files changed, 179 insertions(+), 30 deletions(-) create mode 100644 test/ranges-custom-formatter-test.cc diff --git a/include/fmt/core.h b/include/fmt/core.h index 2bac1cb3..45fd87ee 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -102,6 +102,13 @@ # define FMT_CONSTEXPR20 #endif +// Check if C++20 concepts is supported. +#if ((__cplusplus >= 202002L) && defined(__cpp_lib_concepts)) +# define FMT_HAS_CPP20_CONCEPTS 1 +#else +# define FMT_HAS_CPP20_CONCEPTS 0 +#endif + // Check if constexpr std::char_traits<>::compare,length is supported. #if defined(__GLIBCXX__) # if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \ diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index ee12bd95..a571c37a 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -19,15 +19,20 @@ FMT_BEGIN_NAMESPACE -template struct formatting_range { -#ifdef FMT_DEPRECATED_BRACED_RANGES - Char prefix = '{'; - Char postfix = '}'; -#else - Char prefix = '['; - Char postfix = ']'; +#ifndef FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE +# define FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE 8 #endif +template struct formatting_range { +#ifdef FMT_DEPRECATED_BRACED_RANGES + Char prefix[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = '{'; + Char delimiter[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = ", "; + Char postfix[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = '}'; +#else + Char prefix[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = "["; + Char delimiter[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = ", "; + Char postfix[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = "]"; +#endif template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { return ctx.begin(); @@ -35,9 +40,9 @@ template struct formatting_range { }; template struct formatting_tuple { - Char prefix = '('; - Char postfix = ')'; - + Char prefix[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = "("; + Char delimiter[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = ", "; + Char postfix[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = ")"; template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { return ctx.begin(); @@ -46,15 +51,20 @@ template struct formatting_tuple { namespace detail { -template +template >>>, + char>)> OutputIterator copy(const RangeT& range, OutputIterator out) { for (auto it = range.begin(), end = range.end(); it != end; ++it) *out++ = *it; return out; } -template -OutputIterator copy(const char* str, OutputIterator out) { +template )> +OutputIterator copy(const CharT* str, OutputIterator out) { while (*str) *out++ = *str++; return out; } @@ -221,12 +231,6 @@ template using value_type = remove_cvref_t()))>; -template OutputIt write_delimiter(OutputIt out) { - *out++ = ','; - *out++ = ' '; - return out; -} - struct singleton { unsigned char upper; unsigned char lower_count; @@ -526,6 +530,17 @@ OutputIt write_range_entry(OutputIt out, const Arg& v) { return write(out, v); } +#if FMT_HAS_CPP20_CONCEPTS +template +concept Range = is_range_::value; + +template +concept TupleLike = is_tuple_like_::value; + +template +concept StdStringLike = is_std_string_like::value; +#endif + } // namespace detail template struct is_tuple_like { @@ -533,13 +548,25 @@ template struct is_tuple_like { detail::is_tuple_like_::value && !detail::is_range_::value; }; +#if FMT_HAS_CPP20_CONCEPTS +template +concept TupleLike = detail::TupleLike && !detail::Range; + +// A tuple formatter base. +// This is useful for user code to reuse the range formatting instrument +// by subclassing +template +struct tuple_formatter +#else template -struct formatter::value>> { +struct tuple_formatter +#endif +{ private: // C++11 generic lambda for format() template struct format_each { template void operator()(const T& v) { - if (i > 0) out = detail::write_delimiter(out); + if (i > 0) out = detail::copy(formatting.delimiter, out); out = detail::write_range_entry(out, v); ++i; } @@ -570,6 +597,19 @@ struct formatter::value>> { } }; +// The actual formatter specialization +#if FMT_HAS_CPP20_CONCEPTS +template +struct formatter +#else +template +struct formatter::value>> +#endif + : tuple_formatter { + using tuple_formatter::parse; + using tuple_formatter::format; +}; + template struct is_range { static FMT_CONSTEXPR_DECL const bool value = detail::is_range_::value && !detail::is_std_string_like::value && @@ -577,17 +617,29 @@ template struct is_range { !std::is_constructible, T>::value; }; +#if FMT_HAS_CPP20_CONCEPTS template -struct formatter< - T, Char, - enable_if_t< - fmt::is_range::value +concept Range = detail::Range && !detail::StdStringLike && + !std::is_convertible>::value && + !std::is_constructible, T>::value; + +template +concept FormattableRange = Range // Workaround a bug in MSVC 2019 and earlier. -#if !FMT_MSC_VER - && (is_formattable, Char>::value || - detail::has_fallback_formatter, Char>::value) +# if !FMT_MSC_VER + &&(is_formattable, Char>::value || + detail::has_fallback_formatter, Char>::value) +# endif + ; + +template +requires FormattableRange +struct range_formatter +#else +template +struct range_formatter #endif - >> { +{ formatting_range formatting; template @@ -606,7 +658,7 @@ struct formatter< auto it = std::begin(values); auto end = std::end(values); for (; it != end; ++it) { - if (i > 0) out = detail::write_delimiter(out); + if (i > 0) out = detail::copy(formatting.delimiter, out); out = detail::write_range_entry(out, *it); ++i; } @@ -614,6 +666,29 @@ struct formatter< } }; +// The actual formatter specialization +#if FMT_HAS_CPP20_CONCEPTS +template +requires FormattableRange +struct formatter +#else +template +struct formatter< + T, Char, + enable_if_t< + fmt::is_range::value +// Workaround a bug in MSVC 2019 and earlier. +# if !FMT_MSC_VER + && (is_formattable, Char>::value || + detail::has_fallback_formatter, Char>::value) +# endif + >> +#endif + : range_formatter { + using range_formatter::parse; + using range_formatter::format; +}; + template struct tuple_join_view : detail::view { const std::tuple& tuple; basic_string_view sep; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6a27e33f..9722d977 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -76,6 +76,9 @@ add_fmt_test(color-test) add_fmt_test(core-test) add_fmt_test(gtest-extra-test) add_fmt_test(format-test mock-allocator.h) +if (CMAKE_CXX_STANDARD GREATER_EQUAL 20) + add_fmt_test(ranges-custom-formatter-test) +endif() if (MSVC) target_compile_options(format-test PRIVATE /bigobj) endif () diff --git a/test/ranges-custom-formatter-test.cc b/test/ranges-custom-formatter-test.cc new file mode 100644 index 00000000..0f3a2050 --- /dev/null +++ b/test/ranges-custom-formatter-test.cc @@ -0,0 +1,64 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2018 - present, Remotion (Igor Schulz) +// All Rights Reserved +// {fmt} support for ranges, containers and types tuple interface. + +#include +#include +#include + +#include "fmt/ranges.h" +#include "gtest/gtest.h" + +template struct is_std_map_like : std::false_type {}; + +template +struct is_std_map_like> : std::true_type {}; + +template +struct is_std_map_like> : std::true_type {}; + +template struct is_std_pair : std::false_type {}; +template +struct is_std_pair> : std::true_type {}; + +namespace fmt { + +// specialized map format based on range_formatter +template +requires FormattableRange && + is_std_map_like::value struct formatter + : range_formatter { + using Base = range_formatter; + formatter() : Base{{"{", ", ", "}"}} {} + using Base::format; + using Base::parse; +}; + +// specialized pair format based on tuple_formatter +template +requires fmt::TupleLike && + is_std_pair::value struct formatter : tuple_formatter { + using Base = tuple_formatter; + formatter() : Base{{"{", ": ", "}"}} {} + using Base::format; + using Base::parse; +}; + +} // namespace fmt + +TEST(ranges_custom_formatter_test, format_map) { + auto m = std::map{{"one", 1}, {"two", 2}}; + EXPECT_EQ(fmt::format("{}", m), "{{\"one\": 1}, {\"two\": 2}}"); +} + +TEST(ranges_custom_formatter_test, format_pair) { + auto p = std::pair(42, 1.5f); + EXPECT_EQ(fmt::format("{}", p), "{42: 1.5}"); +}