Skip to content

Commit

Permalink
Range support
Browse files Browse the repository at this point in the history
  • Loading branch information
kitegi committed Oct 27, 2020
1 parent 90071c1 commit c317045
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 9 deletions.
11 changes: 10 additions & 1 deletion include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -1198,7 +1198,11 @@ template <typename Context> struct arg_mapper {
FMT_CONSTEXPR const void* map(void* val) { return val; }
FMT_CONSTEXPR const void* map(const void* val) { return val; }
FMT_CONSTEXPR const void* map(std::nullptr_t val) { return val; }
template <typename T> FMT_CONSTEXPR int map(const T*) {

// We use SFINAE instead of a const T* parameter to avoid conflicting with
// the C array overload.
template <typename T>
FMT_CONSTEXPR auto map(T) -> enable_if_t<std::is_pointer<T>::value, int> {
// Formatting of arbitrary pointers is disallowed. If you want to output
// a pointer cast it to "void *" or "const void *". In particular, this
// forbids formatting of "[const] volatile char *" which is printed as bool
Expand All @@ -1207,6 +1211,11 @@ template <typename Context> struct arg_mapper {
return 0;
}

template <typename T, std::size_t N>
FMT_CONSTEXPR auto map(const T (&values)[N]) -> T const (&)[N] {
return values;
}

template <typename T,
FMT_ENABLE_IF(std::is_enum<T>::value &&
!has_formatter<T, Context>::value &&
Expand Down
106 changes: 98 additions & 8 deletions include/fmt/ranges.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,100 @@ template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};

#if !FMT_MSC_VER || FMT_MSC_VER > 1800

# define FMT_DECLTYPE_RETURN(val) \
->decltype(val) { return val; } \
static_assert( \
true, "") // This makes it so that a semicolon is required after the
// macro, which helps clang-format handle the formatting.

// C array overload
template <typename T, std::size_t N>
auto range_begin(const T (&arr)[N]) -> const T* {
return arr;
}
template <typename T, std::size_t N>
auto range_end(const T (&arr)[N]) -> const T* {
return arr + N;
}

template <typename T, typename Enable = void>
struct has_member_fn_begin_end_t : std::false_type {};

template <typename T>
struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>>
: std::true_type {};

// Member function overload
template <typename T>
struct is_range_<
T, conditional_t<false,
conditional_helper<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>,
void>> : std::true_type {};
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
template <typename T>
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());

// ADL overload. Only participates in overload resolution if member functions
// are not found.
template <typename T>
auto range_begin(T&& rng)
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(begin(static_cast<T&&>(rng)))> {
return begin(static_cast<T&&>(rng));
}
template <typename T>
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(end(static_cast<T&&>(rng)))> {
return end(static_cast<T&&>(rng));
}

template <typename T, typename Enable = void>
struct has_const_begin_end : std::false_type {};
template <typename T, typename Enable = void>
struct has_mutable_begin_end : std::false_type {};

template <typename T>
struct has_const_begin_end<
T, void_t<decltype(detail::range_begin(
std::declval<const remove_cvref_t<T>&>())),
decltype(detail::range_begin(
std::declval<const remove_cvref_t<T>&>()))>>
: std::true_type {};

template <typename T>
struct has_mutable_begin_end<
T, void_t<decltype(detail::range_begin(std::declval<T>())),
decltype(detail::range_begin(std::declval<T>())),
enable_if_t<std::is_copy_constructible<T>::value>>>
: std::true_type {};

template <typename T>
struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {};

template <typename T, typename Enable = void> struct range_to_view;
template <typename T>
struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
struct view_t {
const T* m_range_ptr;

auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
};
static auto view(const T& range) -> view_t { return {&range}; }
};

template <typename T>
struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
has_mutable_begin_end<T>::value>> {
struct view_t {
T m_range_copy;

auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
};
static auto view(const T& range) -> view_t { return {range}; }
};
# undef FMT_DECLTYPE_RETURN
#endif

/// tuple_size and tuple_element check.
Expand Down Expand Up @@ -158,7 +246,8 @@ template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
}

template <typename Range>
using value_type = remove_cvref_t<decltype(*std::declval<Range>().begin())>;
using value_type =
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;

template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
typename std::decay<Arg>::type>::value)>
Expand Down Expand Up @@ -268,8 +357,9 @@ struct formatter<
typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
auto out = detail::copy(formatting.prefix, ctx.out());
size_t i = 0;
auto it = values.begin();
auto end = values.end();
auto view = detail::range_to_view<T>::view(values);
auto it = view.begin();
auto end = view.end();
for (; it != end; ++it) {
if (i > 0) {
if (formatting.add_prepostfix_space) *out++ = ' ';
Expand Down
52 changes: 52 additions & 0 deletions test/ranges-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@
# include <string>
# include <vector>

TEST(RangesTest, FormatArray) {
int32_t ia[] = {1, 2, 3, 5, 7, 11};
auto iaf = fmt::format("{}", ia);
EXPECT_EQ("{1, 2, 3, 5, 7, 11}", iaf);
}

TEST(RangesTest, Format2dArray) {
int32_t ia[][2] = {{1, 2}, {3, 5}, {7, 11}};
auto iaf = fmt::format("{}", ia);
EXPECT_EQ("{{1, 2}, {3, 5}, {7, 11}}", iaf);
}

TEST(RangesTest, FormatVector) {
std::vector<int32_t> iv{1, 2, 3, 5, 7, 11};
auto ivf = fmt::format("{}", iv);
Expand Down Expand Up @@ -177,7 +189,30 @@ template <typename T> class non_const_only_range {
const_iterator end() { return vec.end(); }
};

template <typename T> class noncopyable_range {
private:
std::vector<T> vec;

public:
using const_iterator = typename ::std::vector<T>::const_iterator;

template <typename... Args>
explicit noncopyable_range(Args&&... args)
: vec(::std::forward<Args>(args)...) {}

noncopyable_range(noncopyable_range const&) = delete;
noncopyable_range(noncopyable_range&) = delete;

const_iterator begin() const { return vec.begin(); }
const_iterator end() const { return vec.end(); }
};

TEST(RangesTest, JoinRange) {
noncopyable_range<int> w(3u, 0);
EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(w, ",")));
EXPECT_EQ("0,0,0",
fmt::format("{}", fmt::join(noncopyable_range<int>(3u, 0), ",")));

non_const_only_range<int> x(3u, 0);
EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(x, ",")));
EXPECT_EQ(
Expand All @@ -193,6 +228,23 @@ TEST(RangesTest, JoinRange) {
EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(z, ",")));
}

TEST(RangesTest, Range) {
noncopyable_range<int> w(3u, 0);
EXPECT_EQ("{0, 0, 0}", fmt::format("{}", w));
EXPECT_EQ("{0, 0, 0}", fmt::format("{}", noncopyable_range<int>(3u, 0)));

non_const_only_range<int> x(3u, 0);
EXPECT_EQ("{0, 0, 0}", fmt::format("{}", x));
EXPECT_EQ("{0, 0, 0}", fmt::format("{}", non_const_only_range<int>(3u, 0)));

std::vector<int> y(3u, 0);
EXPECT_EQ("{0, 0, 0}", fmt::format("{}", y));
EXPECT_EQ("{0, 0, 0}", fmt::format("{}", std::vector<int>(3u, 0)));

const std::vector<int> z(3u, 0);
EXPECT_EQ("{0, 0, 0}", fmt::format("{}", z));
}

#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
struct unformattable {};

Expand Down

0 comments on commit c317045

Please sign in to comment.