diff options
author | Justin Riddell <arghnews@hotmail.co.uk> | 2024-05-05 23:44:23 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-05 15:44:23 -0700 |
commit | 10508a30ecd91e5d09a27e4c6c0a01a89fd4edc7 (patch) | |
tree | c4f81248e4d3dcda80748511fd926fde07b82884 | |
parent | 16cec4f5913ef4c5c734290cd0d1eda39cf36bd0 (diff) | |
download | fmtlib-upstream-master.tar.gz |
Enable fmt::join for uncopyable iterators (#3946)upstream-master
If iterator not copyable mutate the underlying iterator
Notably std::ranges::basic_istream_view::__iterator
Addresses issue (#3802)
-rw-r--r-- | include/fmt/ranges.h | 24 | ||||
-rw-r--r-- | test/ranges-test.cc | 53 |
2 files changed, 73 insertions, 4 deletions
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 <iterator> # include <tuple> # include <type_traits> +# include <utility> #endif #include "format.h" @@ -611,7 +612,7 @@ struct join_view : detail::view { basic_string_view<Char> sep; join_view(It b, Sentinel e, basic_string_view<Char> s) - : begin(b), end(e), sep(s) {} + : begin(std::move(b)), end(e), sep(s) {} }; template <typename It, typename Sentinel, typename Char> @@ -631,10 +632,25 @@ struct formatter<join_view<It, Sentinel, Char>, Char> { return value_formatter_.parse(ctx); } - template <typename FormatContext> - 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 do_format(value, ctx, it); + } + + 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 do_format(value, ctx, value.begin); + } + + private: + template <typename FormatContext> + auto do_format(const join_view<It, Sentinel, Char>& 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<join_view<It, Sentinel, Char>, Char> { */ template <typename It, typename Sentinel> auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> { - 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<std::string>(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<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))); +} |