Skip to content

Commit

Permalink
Use mutable join formatter if iterator not copyable
Browse files Browse the repository at this point in the history
Formatting
  • Loading branch information
Arghnews committed May 4, 2024
1 parent 1aaf2a0 commit 913b61a
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 39 deletions.
60 changes: 23 additions & 37 deletions include/fmt/ranges.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@

#include "format.h"

#if FMT_CPLUSPLUS >= 202002L
# define FMT_USE_ITERATOR_CONCEPT
#endif

FMT_BEGIN_NAMESPACE

namespace detail {
Expand Down Expand Up @@ -172,12 +168,11 @@ template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
static auto all_true(...) -> std::false_type;

template <size_t... Is>
static auto check(index_sequence<Is...>)
-> decltype(all_true(
index_sequence<Is...>{},
integer_sequence<
bool, (is_formattable<typename std::tuple_element<Is, T>::type,
C>::value)...>{}));
static auto check(index_sequence<Is...>) -> decltype(all_true(
index_sequence<Is...>{},
integer_sequence<bool,
(is_formattable<typename std::tuple_element<Is, T>::type,
C>::value)...>{}));

public:
static constexpr const bool value =
Expand Down Expand Up @@ -325,8 +320,8 @@ struct formatter<Tuple, Char,
}

template <typename FormatContext>
auto format(const Tuple& value,
FormatContext& ctx) const -> decltype(ctx.out()) {
auto format(const Tuple& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
ctx.advance_to(detail::copy<Char>(opening_bracket_, ctx.out()));
detail::for_each2(
formatters_, value,
Expand Down Expand Up @@ -542,8 +537,8 @@ struct range_default_formatter<
}

template <typename FormatContext>
auto format(range_type& range,
FormatContext& ctx) const -> decltype(ctx.out()) {
auto format(range_type& range, FormatContext& ctx) const
-> decltype(ctx.out()) {
return underlying_.format(range, ctx);
}
};
Expand Down Expand Up @@ -597,29 +592,20 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
return value_formatter_.parse(ctx);
}

#ifdef FMT_USE_ITERATOR_CONCEPT
static constexpr bool is_input_iterator =
std::input_iterator<It> && !std::forward_iterator<It>;
#endif

template <typename FormatContext>
#ifdef FMT_USE_ITERATOR_CONCEPT
requires(!is_input_iterator)
#endif
auto format(const join_view<It, Sentinel, Char>& value,
template <typename FormatContext, typename Iter,
FMT_ENABLE_IF(std::is_copy_constructible<Iter>::value)>
auto format(const join_view<Iter, Sentinel, Char>& value,
FormatContext& ctx) const -> decltype(ctx.out()) {
auto it = value.begin;
return format_impl(value, ctx, it);
}

#ifdef FMT_USE_ITERATOR_CONCEPT
template <typename FormatContext>
requires is_input_iterator
auto format(join_view<It, Sentinel, Char>& value,
FormatContext& ctx) const -> decltype(ctx.out()) {
template <typename FormatContext, typename Iter,
FMT_ENABLE_IF(!std::is_copy_constructible<Iter>::value)>
auto format(join_view<Iter, Sentinel, Char>& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
return format_impl(value, ctx, value.begin);
}
#endif

template <typename FormatContext>
auto format_impl(const join_view<It, Sentinel, Char>& value,
Expand Down Expand Up @@ -665,9 +651,9 @@ auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
\endrst
*/
template <typename Range>
auto join(Range&& r,
string_view sep) -> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> {
auto join(Range&& r, string_view sep)
-> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep};
}

Expand Down Expand Up @@ -798,8 +784,8 @@ FMT_BEGIN_EXPORT
\endrst
*/
template <typename... T>
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
string_view sep) -> tuple_join_view<char, T...> {
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
-> tuple_join_view<char, T...> {
return {tuple, sep};
}

Expand All @@ -815,8 +801,8 @@ FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
\endrst
*/
template <typename T>
auto join(std::initializer_list<T> list,
string_view sep) -> join_view<const T*, const T*> {
auto join(std::initializer_list<T> list, string_view sep)
-> join_view<const T*, const T*> {
return join(std::begin(list), std::end(list), sep);
}

Expand Down
29 changes: 27 additions & 2 deletions test/ranges-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ struct bad_format {};
FMT_BEGIN_NAMESPACE
template <> struct formatter<not_default_formattable> {
auto parse(format_parse_context&) -> const char* { throw bad_format(); }
auto format(not_default_formattable,
format_context& ctx) -> format_context::iterator {
auto format(not_default_formattable, format_context& ctx)
-> format_context::iterator {
return ctx.out();
}
};
Expand Down Expand Up @@ -651,3 +651,28 @@ TEST(ranges_test, input_range_join_overload) {
fmt::format("{}", fmt::join(std::views::istream<std::string>(iss), ".")));
}
#endif

TEST(ranges_test, std_istream_iterator_join) {
std::istringstream iss("1 2 3 4 5");
std::istream_iterator<int> first{iss};
std::istream_iterator<int> last{};
auto joined_view = fmt::join(first, last, ", ");
EXPECT_EQ("1, 2, 3, 4, 5", fmt::format("{}", std::move(joined_view)));
}

TEST(ranges_test, movable_only_istream_iter_join) {
// Mirrors c++20 std::ranges::basic_istream_view::iterator
struct UncopyableIstreamIter : std::istream_iterator<int> {
explicit UncopyableIstreamIter(std::istringstream& iss)
: std::istream_iterator<int>{iss} {}
UncopyableIstreamIter(const UncopyableIstreamIter&) = delete;
UncopyableIstreamIter(UncopyableIstreamIter&&) = default;
};
static_assert(!std::is_copy_constructible<UncopyableIstreamIter>::value, "");

std::istringstream iss("1 2 3 4 5");
UncopyableIstreamIter first{iss};
std::istream_iterator<int> last{};
auto joined_view = fmt::join(std::move(first), last, ", ");
EXPECT_EQ("1, 2, 3, 4, 5", fmt::format("{}", std::move(joined_view)));
}

0 comments on commit 913b61a

Please sign in to comment.