aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Riddell <arghnews@hotmail.co.uk>2024-05-05 23:44:23 +0100
committerGitHub <noreply@github.com>2024-05-05 15:44:23 -0700
commit10508a30ecd91e5d09a27e4c6c0a01a89fd4edc7 (patch)
treec4f81248e4d3dcda80748511fd926fde07b82884
parent16cec4f5913ef4c5c734290cd0d1eda39cf36bd0 (diff)
downloadfmtlib-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.h24
-rw-r--r--test/ranges-test.cc53
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)));
+}