diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 3d9bcaf0..05712db6 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -13,6 +13,7 @@ # include # include # include +# include #endif #include "format.h" @@ -611,7 +612,7 @@ struct join_view : detail::view { basic_string_view sep; join_view(It b, Sentinel e, basic_string_view s) - : begin(b), end(e), sep(s) {} + : begin(std::move(b)), end(e), sep(s) {} }; template @@ -631,10 +632,25 @@ struct formatter, Char> { return value_formatter_.parse(ctx); } - template - auto format(const join_view& value, + template ::value)> + auto format(const join_view& value, FormatContext& ctx) const -> decltype(ctx.out()) { auto it = value.begin; + return do_format(value, ctx, it); + } + + template ::value)> + auto format(join_view& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + return do_format(value, ctx, value.begin); + } + + private: + template + auto do_format(const join_view& value, FormatContext& ctx, + It& it) const -> decltype(ctx.out()) { auto out = ctx.out(); if (it != value.end) { out = value_formatter_.format(*it, ctx); @@ -656,7 +672,7 @@ struct formatter, Char> { */ template auto join(It begin, Sentinel end, string_view sep) -> join_view { - return {begin, end, sep}; + return {std::move(begin), end, sep}; } /** diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 466e5848..67bf44f9 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -652,3 +652,56 @@ struct lvalue_qualified_begin_end { TEST(ranges_test, lvalue_qualified_begin_end) { EXPECT_EQ(fmt::format("{}", lvalue_qualified_begin_end{}), "[1, 2, 3, 4, 5]"); } + +#if !defined(__cpp_lib_ranges) || __cpp_lib_ranges <= 202106L +# define ENABLE_INPUT_RANGE_JOIN_TEST 0 +#elif FMT_CLANG_VERSION +# if FMT_CLANG_VERSION > 1500 +# define ENABLE_INPUT_RANGE_JOIN_TEST 1 +# else +# define ENABLE_INPUT_RANGE_JOIN_TEST 0 +# endif +#else +# define ENABLE_INPUT_RANGE_JOIN_TEST 1 +#endif + +#if ENABLE_INPUT_RANGE_JOIN_TEST +TEST(ranges_test, input_range_join) { + std::istringstream iss("1 2 3 4 5"); + auto view = std::views::istream(iss); + auto joined_view = fmt::join(view.begin(), view.end(), ", "); + EXPECT_EQ("1, 2, 3, 4, 5", fmt::format("{}", std::move(joined_view))); +} + +TEST(ranges_test, input_range_join_overload) { + std::istringstream iss("1 2 3 4 5"); + EXPECT_EQ( + "1.2.3.4.5", + fmt::format("{}", fmt::join(std::views::istream(iss), "."))); +} +#endif + +TEST(ranges_test, std_istream_iterator_join) { + std::istringstream iss("1 2 3 4 5"); + std::istream_iterator first{iss}; + std::istream_iterator 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 { + explicit UncopyableIstreamIter(std::istringstream& iss) + : std::istream_iterator{iss} {} + UncopyableIstreamIter(const UncopyableIstreamIter&) = delete; + UncopyableIstreamIter(UncopyableIstreamIter&&) = default; + }; + static_assert(!std::is_copy_constructible::value, ""); + + std::istringstream iss("1 2 3 4 5"); + UncopyableIstreamIter first{iss}; + std::istream_iterator last{}; + auto joined_view = fmt::join(std::move(first), last, ", "); + EXPECT_EQ("1, 2, 3, 4, 5", fmt::format("{}", std::move(joined_view))); +}