Add concepts supoort for ranges formatter; Allow customize prefix, delimiter and postfix.

This commit is contained in:
Jerry Ma 2021-09-28 23:36:34 -04:00
parent 927dbd134d
commit 670f8c074c
4 changed files with 179 additions and 30 deletions

View File

@ -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) && \

View File

@ -19,15 +19,20 @@
FMT_BEGIN_NAMESPACE
template <typename Char, typename Enable = void> 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 <typename Char, typename Enable = void> 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 <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
@ -35,9 +40,9 @@ template <typename Char, typename Enable = void> struct formatting_range {
};
template <typename Char, typename Enable = void> 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 <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
@ -46,15 +51,20 @@ template <typename Char, typename Enable = void> struct formatting_tuple {
namespace detail {
template <typename RangeT, typename OutputIterator>
template <typename RangeT, typename OutputIterator,
FMT_ENABLE_IF(
!std::is_same_v<std::remove_cv_t<std::remove_reference_t<
std::remove_pointer_t<std::decay_t<RangeT>>>>,
char>)>
OutputIterator copy(const RangeT& range, OutputIterator out) {
for (auto it = range.begin(), end = range.end(); it != end; ++it)
*out++ = *it;
return out;
}
template <typename OutputIterator>
OutputIterator copy(const char* str, OutputIterator out) {
template <typename CharT, typename OutputIterator,
FMT_ENABLE_IF(std::is_same_v<CharT, char>)>
OutputIterator copy(const CharT* str, OutputIterator out) {
while (*str) *out++ = *str++;
return out;
}
@ -221,12 +231,6 @@ template <typename Range>
using value_type =
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
template <typename OutputIt> 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<Char>(out, v);
}
#if FMT_HAS_CPP20_CONCEPTS
template <typename T>
concept Range = is_range_<T>::value;
template <typename T>
concept TupleLike = is_tuple_like_<T>::value;
template <typename T>
concept StdStringLike = is_std_string_like<T>::value;
#endif
} // namespace detail
template <typename T> struct is_tuple_like {
@ -533,13 +548,25 @@ template <typename T> struct is_tuple_like {
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
};
#if FMT_HAS_CPP20_CONCEPTS
template <typename T>
concept TupleLike = detail::TupleLike<T> && !detail::Range<T>;
// A tuple formatter base.
// This is useful for user code to reuse the range formatting instrument
// by subclassing
template <TupleLike TupleT, typename Char>
struct tuple_formatter
#else
template <typename TupleT, typename Char>
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
struct tuple_formatter
#endif
{
private:
// 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::write_delimiter(out);
if (i > 0) out = detail::copy(formatting.delimiter, out);
out = detail::write_range_entry<Char>(out, v);
++i;
}
@ -570,6 +597,19 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
}
};
// The actual formatter specialization
#if FMT_HAS_CPP20_CONCEPTS
template <TupleLike TupleT, typename Char>
struct formatter<TupleT, Char>
#else
template <typename TupleT, typename Char>
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>>
#endif
: tuple_formatter<TupleT, Char> {
using tuple_formatter<TupleT, Char>::parse;
using tuple_formatter<TupleT, Char>::format;
};
template <typename T, typename Char> struct is_range {
static FMT_CONSTEXPR_DECL const bool value =
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
@ -577,17 +617,29 @@ template <typename T, typename Char> struct is_range {
!std::is_constructible<detail::std_string_view<Char>, T>::value;
};
#if FMT_HAS_CPP20_CONCEPTS
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<
fmt::is_range<T, Char>::value
concept Range = detail::Range<T> && !detail::StdStringLike<T> &&
!std::is_convertible<T, std::basic_string<Char>>::value &&
!std::is_constructible<detail::std_string_view<Char>, T>::value;
template <typename T, typename Char>
concept FormattableRange = Range<T, Char>
// Workaround a bug in MSVC 2019 and earlier.
#if !FMT_MSC_VER
&& (is_formattable<detail::value_type<T>, Char>::value ||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
# if !FMT_MSC_VER
&&(is_formattable<detail::value_type<T>, Char>::value ||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
# endif
;
template <typename T, typename Char>
requires FormattableRange<T, Char>
struct range_formatter
#else
template <typename T, typename Char>
struct range_formatter
#endif
>> {
{
formatting_range<Char> formatting;
template <typename ParseContext>
@ -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<Char>(out, *it);
++i;
}
@ -614,6 +666,29 @@ struct formatter<
}
};
// The actual formatter specialization
#if FMT_HAS_CPP20_CONCEPTS
template <typename T, typename Char>
requires FormattableRange<T, Char>
struct formatter<T, Char>
#else
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<
fmt::is_range<T, Char>::value
// Workaround a bug in MSVC 2019 and earlier.
# if !FMT_MSC_VER
&& (is_formattable<detail::value_type<T>, Char>::value ||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
# endif
>>
#endif
: range_formatter<T, Char> {
using range_formatter<T, Char>::parse;
using range_formatter<T, Char>::format;
};
template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep;

View File

@ -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 ()

View File

@ -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 <map>
#include <string>
#include <unordered_map>
#include "fmt/ranges.h"
#include "gtest/gtest.h"
template <typename T> struct is_std_map_like : std::false_type {};
template <typename... Ts>
struct is_std_map_like<std::map<Ts...>> : std::true_type {};
template <typename... Ts>
struct is_std_map_like<std::unordered_map<Ts...>> : std::true_type {};
template <typename T> struct is_std_pair : std::false_type {};
template <typename... Ts>
struct is_std_pair<std::pair<Ts...>> : std::true_type {};
namespace fmt {
// specialized map format based on range_formatter
template <typename T, typename Char>
requires FormattableRange<T, Char> &&
is_std_map_like<T>::value struct formatter<T, Char>
: range_formatter<T, Char> {
using Base = range_formatter<T, Char>;
formatter() : Base{{"{", ", ", "}"}} {}
using Base::format;
using Base::parse;
};
// specialized pair format based on tuple_formatter
template <typename T, typename Char>
requires fmt::TupleLike<T> &&
is_std_pair<T>::value struct formatter<T, Char> : tuple_formatter<T, Char> {
using Base = tuple_formatter<T, Char>;
formatter() : Base{{"{", ": ", "}"}} {}
using Base::format;
using Base::parse;
};
} // namespace fmt
TEST(ranges_custom_formatter_test, format_map) {
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
EXPECT_EQ(fmt::format("{}", m), "{{\"one\": 1}, {\"two\": 2}}");
}
TEST(ranges_custom_formatter_test, format_pair) {
auto p = std::pair<int, float>(42, 1.5f);
EXPECT_EQ(fmt::format("{}", p), "{42: 1.5}");
}