-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for ranges, containers and tuple-like types in fmt/ranges.h
- Loading branch information
Showing
3 changed files
with
320 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
// 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. | ||
|
||
#ifndef FMT_RANGES_H_ | ||
#define FMT_RANGES_H_ | ||
|
||
#include "format.h" | ||
#include <type_traits> | ||
|
||
// output only up to N items from the range. | ||
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT | ||
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256 | ||
#endif | ||
|
||
FMT_BEGIN_NAMESPACE | ||
|
||
template <typename Char> | ||
struct formatting_base { | ||
template <typename ParseContext> | ||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { | ||
return ctx.begin(); | ||
} | ||
}; | ||
|
||
template <typename Char, typename Enable = void> | ||
struct formatting_range : formatting_base<Char> { | ||
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit = | ||
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the range. | ||
Char prefix = '{'; | ||
Char delimiter = ','; | ||
Char postfix = '}'; | ||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; | ||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; | ||
}; | ||
|
||
template <typename Char, typename Enable = void> | ||
struct formatting_tuple : formatting_base<Char> { | ||
Char prefix = '('; | ||
Char delimiter = ','; | ||
Char postfix = ')'; | ||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; | ||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; | ||
}; | ||
|
||
namespace internal { | ||
|
||
template <typename RangeT, typename OutputIterator> | ||
void copy(const RangeT &range, OutputIterator out) { | ||
for (const auto &it : range) { | ||
*out++ = it; | ||
} | ||
} | ||
|
||
template <typename OutputIterator> | ||
void copy(const char *str, OutputIterator out) { | ||
const char *p_curr = str; | ||
while (*p_curr) { | ||
*out++ = *p_curr++; | ||
} | ||
} | ||
|
||
template <typename OutputIterator> | ||
void copy(char ch, OutputIterator out) { | ||
*out++ = ch; | ||
} | ||
|
||
/// Return true value if T has std::string interface, like std::string_view. | ||
template <typename T> | ||
class is_like_std_string { | ||
template <typename U> | ||
static auto check(U *p) -> | ||
decltype(p->find('a'), p->length(), p->data(), int()); | ||
template <typename> | ||
static void check(...); | ||
|
||
public: | ||
static FMT_CONSTEXPR_DECL const bool value = | ||
!std::is_void<decltype(check<T>(nullptr))>::value; | ||
}; | ||
|
||
template <typename... Ts> | ||
struct conditional_helper {}; | ||
|
||
template <typename T, typename _ = void> | ||
struct is_range_ : std::false_type {}; | ||
|
||
template <typename T> | ||
struct is_range_<T,typename std::conditional< | ||
false, | ||
conditional_helper<decltype(std::declval<T>().begin()), | ||
decltype(std::declval<T>().end())>, | ||
void>::type> : std::true_type {}; | ||
|
||
template <typename T> | ||
struct is_range { | ||
static FMT_CONSTEXPR_DECL const bool value = | ||
is_range_<T>::value && !is_like_std_string<T>::value; | ||
}; | ||
|
||
/// tuple_size and tuple_element check. | ||
template <typename T> | ||
class is_tuple_like_ { | ||
template <typename U> | ||
static auto check(U *p) -> | ||
decltype(std::tuple_size<U>::value, | ||
std::declval<typename std::tuple_element<0, U>::type>(), int()); | ||
template <typename> | ||
static void check(...); | ||
|
||
public: | ||
static FMT_CONSTEXPR_DECL const bool value = | ||
!std::is_void<decltype(check<T>(nullptr))>::value; | ||
}; | ||
|
||
template <typename T> | ||
struct is_tuple_like { | ||
static FMT_CONSTEXPR_DECL const bool value = | ||
is_tuple_like_<T>::value && !is_range_<T>::value; | ||
}; | ||
|
||
template <size_t... Is, class Tuple, class F> | ||
void for_each(std::index_sequence<Is...>, Tuple &&tup, F &&f) noexcept { | ||
using std::get; | ||
// using free function get<I>(T) now. | ||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...}; | ||
(void)_; // blocks warnings | ||
} | ||
|
||
template <class T> | ||
FMT_CONSTEXPR std::make_index_sequence<std::tuple_size<T>::value> | ||
get_indexes(T const &) { return {}; } | ||
|
||
template <class Tuple, class F> | ||
void for_each(Tuple &&tup, F &&f) { | ||
const auto indexes = get_indexes(tup); | ||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); | ||
} | ||
} // namespace internal | ||
|
||
template <typename TupleT, typename Char> | ||
struct formatter<TupleT, Char, | ||
typename std::enable_if<fmt::internal::is_tuple_like<TupleT>::value>::type> { | ||
|
||
fmt::formatting_tuple<Char> formatting; | ||
|
||
template <typename ParseContext> | ||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { | ||
return formatting.parse(ctx); | ||
} | ||
|
||
template <typename FormatContext = format_context> | ||
auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) { | ||
auto out = ctx.out(); | ||
std::size_t i = 0; | ||
internal::copy(formatting.prefix, out); | ||
internal::for_each(values, [&](const auto &v) { | ||
if (i > 0) { | ||
if (formatting.add_prepostfix_space) { | ||
*out++ = ' '; | ||
} | ||
internal::copy(formatting.delimiter, out); | ||
} | ||
if (formatting.add_delimiter_spaces && i > 0) { | ||
format_to(out, " {}", v); | ||
} else { | ||
format_to(out, "{}", v); | ||
} | ||
++i; | ||
}); | ||
if (formatting.add_prepostfix_space) { | ||
*out++ = ' '; | ||
} | ||
internal::copy(formatting.postfix, out); | ||
|
||
return ctx.out(); | ||
} | ||
}; | ||
|
||
template <typename RangeT, typename Char> | ||
struct formatter< RangeT, Char, | ||
typename std::enable_if<fmt::internal::is_range<RangeT>::value>::type> { | ||
|
||
fmt::formatting_range<Char> formatting; | ||
|
||
template <typename ParseContext> | ||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { | ||
return formatting.parse(ctx); | ||
} | ||
|
||
template <typename FormatContext> | ||
auto format(const RangeT &values, FormatContext &ctx) -> decltype(ctx.out()) { | ||
auto out = ctx.out(); | ||
internal::copy(formatting.prefix, out); | ||
std::size_t i = 0; | ||
for (const auto &it : values) { | ||
if (i > 0) { | ||
if (formatting.add_prepostfix_space) { | ||
*out++ = ' '; | ||
} | ||
internal::copy(formatting.delimiter, out); | ||
} | ||
if (formatting.add_delimiter_spaces && i > 0) { | ||
format_to(out, " {}", it); | ||
} else { | ||
format_to(out, "{}", it); | ||
} | ||
if (++i > formatting.range_length_limit) { | ||
format_to(out, " ... <other elements>"); | ||
break; | ||
} | ||
} | ||
if (formatting.add_prepostfix_space) { | ||
*out++ = ' '; | ||
} | ||
internal::copy(formatting.postfix, out); | ||
return ctx.out(); | ||
} | ||
}; | ||
|
||
FMT_END_NAMESPACE | ||
|
||
#endif // FMT_RANGES_H_ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// 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 "fmt/ranges.h" | ||
|
||
#include "gtest/gtest.h" | ||
|
||
#include <vector> | ||
#include <array> | ||
#include <map> | ||
#include <string> | ||
|
||
TEST(RangesTest, FormatVector) { | ||
std::vector<int32_t> iv{1, 2, 3, 5, 7, 11}; | ||
auto ivf = fmt::format("{}", iv); | ||
EXPECT_EQ("{1, 2, 3, 5, 7, 11}", ivf); | ||
} | ||
|
||
TEST(RangesTest, FormatVector2) { | ||
std::vector<std::vector<int32_t>> ivv{{1, 2}, {3, 5}, {7, 11}}; | ||
auto ivf = fmt::format("{}", ivv); | ||
EXPECT_EQ("{{1, 2}, {3, 5}, {7, 11}}", ivf); | ||
} | ||
|
||
TEST(RangesTest, FormatMap) { | ||
std::map<std::string, int32_t> simap{{"one", 1}, {"two", 2}}; | ||
EXPECT_EQ("{(one, 1), (two, 2)}", fmt::format("{}", simap)); | ||
} | ||
|
||
TEST(RangesTest, FormatPair) { | ||
std::pair<int64_t, float> pa1{42, 3.14159265358979f}; | ||
EXPECT_EQ("(42, 3.14159)", fmt::format("{}", pa1)); | ||
} | ||
|
||
TEST(RangesTest, FormatTuple) { | ||
std::tuple<int64_t, float, std::string> tu1{42, 3.14159265358979f, | ||
"this is tuple"}; | ||
EXPECT_EQ("(42, 3.14159, this is tuple)", fmt::format("{}", tu1)); | ||
} | ||
|
||
/// check if 'if constexpr' is supported. | ||
#if (__cplusplus > 201402L) || \ | ||
(defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) | ||
|
||
struct my_struct { | ||
int32_t i; | ||
std::string str; // can throw | ||
template <std::size_t N> | ||
decltype(auto) get() const noexcept { | ||
if constexpr (N == 0) | ||
return i; | ||
else if constexpr (N == 1) | ||
return fmt::string_view{str}; | ||
} | ||
}; | ||
|
||
template <std::size_t N> | ||
decltype(auto) get(const my_struct& s) noexcept { | ||
return s.get<N>(); | ||
} | ||
|
||
namespace std { | ||
|
||
template <> | ||
struct tuple_size<my_struct> : std::integral_constant<std::size_t, 2> {}; | ||
|
||
template <std::size_t N> | ||
struct tuple_element<N, my_struct> { | ||
using type = decltype(std::declval<my_struct>().get<N>()); | ||
}; | ||
|
||
} // namespace std | ||
|
||
TEST(RangesTest, FormatStruct) { | ||
my_struct mst{13, "my struct"}; | ||
EXPECT_EQ("(13, my struct)", fmt::format("{}", mst)); | ||
} | ||
|
||
#endif // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG > | ||
// 201402L && _MSC_VER >= 1910) |