Add concepts supoort for ranges formatter; Allow customize prefix, delimiter and postfix.
This commit is contained in:
parent
927dbd134d
commit
670f8c074c
@ -102,6 +102,13 @@
|
|||||||
# define FMT_CONSTEXPR20
|
# define FMT_CONSTEXPR20
|
||||||
#endif
|
#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.
|
// Check if constexpr std::char_traits<>::compare,length is supported.
|
||||||
#if defined(__GLIBCXX__)
|
#if defined(__GLIBCXX__)
|
||||||
# if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \
|
# if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \
|
||||||
|
|||||||
@ -19,15 +19,20 @@
|
|||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
template <typename Char, typename Enable = void> struct formatting_range {
|
#ifndef FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE
|
||||||
#ifdef FMT_DEPRECATED_BRACED_RANGES
|
# define FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE 8
|
||||||
Char prefix = '{';
|
|
||||||
Char postfix = '}';
|
|
||||||
#else
|
|
||||||
Char prefix = '[';
|
|
||||||
Char postfix = ']';
|
|
||||||
#endif
|
#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>
|
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();
|
||||||
@ -35,9 +40,9 @@ template <typename Char, typename Enable = void> struct formatting_range {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename Enable = void> struct formatting_tuple {
|
template <typename Char, typename Enable = void> struct formatting_tuple {
|
||||||
Char prefix = '(';
|
Char prefix[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = "(";
|
||||||
Char postfix = ')';
|
Char delimiter[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = ", ";
|
||||||
|
Char postfix[FMT_RANGES_FORMATTING_CLASS_MEMBER_SIZE] = ")";
|
||||||
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,15 +51,20 @@ template <typename Char, typename Enable = void> struct formatting_tuple {
|
|||||||
|
|
||||||
namespace detail {
|
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) {
|
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
||||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||||
*out++ = *it;
|
*out++ = *it;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIterator>
|
template <typename CharT, typename OutputIterator,
|
||||||
OutputIterator copy(const char* str, OutputIterator out) {
|
FMT_ENABLE_IF(std::is_same_v<CharT, char>)>
|
||||||
|
OutputIterator copy(const CharT* str, OutputIterator out) {
|
||||||
while (*str) *out++ = *str++;
|
while (*str) *out++ = *str++;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@ -221,12 +231,6 @@ template <typename Range>
|
|||||||
using value_type =
|
using value_type =
|
||||||
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
|
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
|
||||||
|
|
||||||
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
|
|
||||||
*out++ = ',';
|
|
||||||
*out++ = ' ';
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct singleton {
|
struct singleton {
|
||||||
unsigned char upper;
|
unsigned char upper;
|
||||||
unsigned char lower_count;
|
unsigned char lower_count;
|
||||||
@ -526,6 +530,17 @@ OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
|||||||
return write<Char>(out, 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
|
} // namespace detail
|
||||||
|
|
||||||
template <typename T> struct is_tuple_like {
|
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;
|
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>
|
template <typename TupleT, typename Char>
|
||||||
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
struct tuple_formatter
|
||||||
|
#endif
|
||||||
|
{
|
||||||
private:
|
private:
|
||||||
// C++11 generic lambda for format()
|
// C++11 generic lambda for format()
|
||||||
template <typename FormatContext> struct format_each {
|
template <typename FormatContext> struct format_each {
|
||||||
template <typename T> void operator()(const T& v) {
|
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);
|
out = detail::write_range_entry<Char>(out, v);
|
||||||
++i;
|
++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 {
|
template <typename T, typename Char> struct is_range {
|
||||||
static FMT_CONSTEXPR_DECL const bool value =
|
static FMT_CONSTEXPR_DECL const bool value =
|
||||||
detail::is_range_<T>::value && !detail::is_std_string_like<T>::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;
|
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if FMT_HAS_CPP20_CONCEPTS
|
||||||
template <typename T, typename Char>
|
template <typename T, typename Char>
|
||||||
struct formatter<
|
concept Range = detail::Range<T> && !detail::StdStringLike<T> &&
|
||||||
T, Char,
|
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||||
enable_if_t<
|
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||||
fmt::is_range<T, Char>::value
|
|
||||||
|
template <typename T, typename Char>
|
||||||
|
concept FormattableRange = Range<T, Char>
|
||||||
// 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::value_type<T>, Char>::value ||
|
||||||
detail::has_fallback_formatter<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
|
#endif
|
||||||
>> {
|
{
|
||||||
formatting_range<Char> formatting;
|
formatting_range<Char> formatting;
|
||||||
|
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
@ -606,7 +658,7 @@ struct formatter<
|
|||||||
auto it = std::begin(values);
|
auto it = std::begin(values);
|
||||||
auto end = std::end(values);
|
auto end = std::end(values);
|
||||||
for (; it != end; ++it) {
|
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);
|
out = detail::write_range_entry<Char>(out, *it);
|
||||||
++i;
|
++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 {
|
template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
||||||
const std::tuple<T...>& tuple;
|
const std::tuple<T...>& tuple;
|
||||||
basic_string_view<Char> sep;
|
basic_string_view<Char> sep;
|
||||||
|
|||||||
@ -76,6 +76,9 @@ add_fmt_test(color-test)
|
|||||||
add_fmt_test(core-test)
|
add_fmt_test(core-test)
|
||||||
add_fmt_test(gtest-extra-test)
|
add_fmt_test(gtest-extra-test)
|
||||||
add_fmt_test(format-test mock-allocator.h)
|
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)
|
if (MSVC)
|
||||||
target_compile_options(format-test PRIVATE /bigobj)
|
target_compile_options(format-test PRIVATE /bigobj)
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
64
test/ranges-custom-formatter-test.cc
Normal file
64
test/ranges-custom-formatter-test.cc
Normal 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}");
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user