mirror of
https://github.com/fmtlib/fmt.git
synced 2024-11-10 05:10:05 +00:00
Enable fmt::join for uncopyable iterators (#3946)
If iterator not copyable mutate the underlying iterator Notably std::ranges::basic_istream_view::__iterator Addresses issue (#3802)
This commit is contained in:
parent
16cec4f591
commit
10508a30ec
@ -13,6 +13,7 @@
|
|||||||
# include <iterator>
|
# include <iterator>
|
||||||
# include <tuple>
|
# include <tuple>
|
||||||
# include <type_traits>
|
# include <type_traits>
|
||||||
|
# include <utility>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
@ -611,7 +612,7 @@ struct join_view : detail::view {
|
|||||||
basic_string_view<Char> sep;
|
basic_string_view<Char> sep;
|
||||||
|
|
||||||
join_view(It b, Sentinel e, basic_string_view<Char> s)
|
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>
|
template <typename It, typename Sentinel, typename Char>
|
||||||
@ -631,10 +632,25 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
|
|||||||
return value_formatter_.parse(ctx);
|
return value_formatter_.parse(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext, typename Iter,
|
||||||
auto format(const join_view<It, Sentinel, Char>& value,
|
FMT_ENABLE_IF(std::is_copy_constructible<Iter>::value)>
|
||||||
|
auto format(const join_view<Iter, Sentinel, Char>& value,
|
||||||
FormatContext& ctx) const -> decltype(ctx.out()) {
|
FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||||
auto it = value.begin;
|
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();
|
auto out = ctx.out();
|
||||||
if (it != value.end) {
|
if (it != value.end) {
|
||||||
out = value_formatter_.format(*it, ctx);
|
out = value_formatter_.format(*it, ctx);
|
||||||
@ -656,7 +672,7 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
|
|||||||
*/
|
*/
|
||||||
template <typename It, typename Sentinel>
|
template <typename It, typename Sentinel>
|
||||||
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
|
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
|
||||||
return {begin, end, sep};
|
return {std::move(begin), end, sep};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -652,3 +652,56 @@ struct lvalue_qualified_begin_end {
|
|||||||
TEST(ranges_test, lvalue_qualified_begin_end) {
|
TEST(ranges_test, lvalue_qualified_begin_end) {
|
||||||
EXPECT_EQ(fmt::format("{}", lvalue_qualified_begin_end{}), "[1, 2, 3, 4, 5]");
|
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)));
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user