Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ranges, containers and types tuple interface... #735

Closed
wants to merge 13 commits into from
Closed
236 changes: 236 additions & 0 deletions include/fmt/ranges.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// 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;
}

} // namespace internal

namespace internal {

/// 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_
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ add_fmt_test(printf-test)
add_fmt_test(time-test)
add_fmt_test(util-test mock-allocator.h)
add_fmt_test(custom-formatter-test)
add_fmt_test(ranges-test)

# Enable stricter options for one test to make sure that the header is free of
# warnings.
Expand Down
88 changes: 88 additions & 0 deletions test/ranges-test.cc
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)