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

Range support #1953

Merged
merged 1 commit into from
Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]) -> const T (&)[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