diff --git a/include/fmt/format.h b/include/fmt/format.h index c033c1f1..a81c155b 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -291,6 +291,7 @@ template <> constexpr int num_bits() { // An approximation of iterator_t for pre-C++20 systems. template using iterator_t = decltype(std::begin(std::declval())); +template using sentinel_t = decltype(std::end(std::declval())); // Detect the iterator category of *any* given type in a SFINAE-friendly way. // Unfortunately, older implementations of std::iterator_traits are not safe @@ -3215,19 +3216,21 @@ template <> struct formatter { detail::dynamic_format_specs specs_; }; -template struct arg_join : detail::view { +template +struct arg_join : detail::view { It begin; - It end; + Sentinel end; basic_string_view sep; - arg_join(It b, It e, basic_string_view s) : begin(b), end(e), sep(s) {} + arg_join(It b, Sentinel e, basic_string_view s) + : begin(b), end(e), sep(s) {} }; -template -struct formatter, Char> +template +struct formatter, Char> : formatter::value_type, Char> { template - auto format(const arg_join& value, FormatContext& ctx) + auto format(const arg_join& value, FormatContext& ctx) -> decltype(ctx.out()) { using base = formatter::value_type, Char>; auto it = value.begin; @@ -3248,13 +3251,13 @@ struct formatter, Char> Returns an object that formats the iterator range `[begin, end)` with elements separated by `sep`. */ -template -arg_join join(It begin, It end, string_view sep) { +template +arg_join join(It begin, Sentinel end, string_view sep) { return {begin, end, sep}; } -template -arg_join join(It begin, It end, wstring_view sep) { +template +arg_join join(It begin, Sentinel end, wstring_view sep) { return {begin, end, sep}; } @@ -3275,14 +3278,15 @@ arg_join join(It begin, It end, wstring_view sep) { \endrst */ template -arg_join, char> join(const Range& range, - string_view sep) { +arg_join, detail::sentinel_t, char> +join(const Range& range, string_view sep) { return join(std::begin(range), std::end(range), sep); } template -arg_join, wchar_t> join(const Range& range, - wstring_view sep) { +arg_join, detail::sentinel_t, + wchar_t> +join(const Range& range, wstring_view sep) { return join(std::begin(range), std::end(range), sep); } diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index c58b97a1..c48f1727 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -261,7 +261,9 @@ struct formatter 0) { if (formatting.add_prepostfix_space) *out++ = ' '; out = detail::copy(formatting.delimiter, out); @@ -368,14 +370,14 @@ FMT_CONSTEXPR tuple_arg_join join(const std::tuple& tuple, \endrst */ template -arg_join>, char> join( - std::initializer_list list, string_view sep) { +arg_join join(std::initializer_list list, + string_view sep) { return join(std::begin(list), std::end(list), sep); } template -arg_join>, wchar_t> join( - std::initializer_list list, wstring_view sep) { +arg_join join(std::initializer_list list, + wstring_view sep) { return join(std::begin(list), std::end(list), sep); } diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 462ba7f6..46208e8d 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -139,3 +139,17 @@ TEST(RangesTest, FormatStringLike) { EXPECT_EQ("foo", fmt::format("{}", string_like())); } #endif // FMT_USE_STRING_VIEW + +struct zstring_sentinel {}; +bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; } +bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; } +struct zstring { + const char* p; + const char* begin() const { return p; } + zstring_sentinel end() const { return {}; } +}; +TEST(RangesTest, JoinSentinel) { + zstring hello{"hello"}; + EXPECT_EQ("{'h', 'e', 'l', 'l', 'o'}", fmt::format("{}", hello)); + EXPECT_EQ("h_e_l_l_o", fmt::format("{}", fmt::join(hello, "_"))); +}