From 13ec66bf78815ca791e24a36e96a0edb9f98184b Mon Sep 17 00:00:00 2001 From: The Phantom Derpstorm Date: Tue, 16 Jan 2024 10:01:15 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A0=20Add=20basic=20array=20safety=20f?= =?UTF-8?q?unctions=20and=20backwards-compatible=20result=20type=20(#3805)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/fmt/base.h | 44 ++++++++++++++++++++++++++++++++++++++------ test/base-test.cc | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index f1d853f3..de86bed7 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -276,6 +276,8 @@ # define FMT_UNICODE !FMT_MSC_VERSION #endif +#define FMT_FWD(...) static_cast(__VA_ARGS__) + // Enable minimal optimizations for more compact code in debug mode. FMT_GCC_PRAGMA("GCC push_options") #if !defined(__OPTIMIZE__) && !defined(__CUDACC__) @@ -2861,8 +2863,10 @@ inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } /** Formats a string and writes the output to ``out``. */ template ::value)> -auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { + FMT_ENABLE_IF(detail::is_output_iterator, + char>::value)> +auto vformat_to(OutputIt&& out, string_view fmt, format_args args) + -> remove_cvref_t { auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, fmt, args, {}); return detail::get_iterator(buf, out); @@ -2881,10 +2885,11 @@ auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { \endrst */ template ::value)> -FMT_INLINE auto format_to(OutputIt out, format_string fmt, T&&... args) - -> OutputIt { - return vformat_to(out, fmt, fmt::make_format_args(args...)); + FMT_ENABLE_IF(detail::is_output_iterator, + char>::value)> +FMT_INLINE auto format_to(OutputIt&& out, format_string fmt, T&&... args) + -> remove_cvref_t { + return vformat_to(FMT_FWD(out), fmt, fmt::make_format_args(args...)); } template struct format_to_n_result { @@ -2919,6 +2924,33 @@ FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, return vformat_to_n(out, n, fmt, fmt::make_format_args(args...)); } +template +struct format_to_result { + /** Iterator pointing to just after the last succesful write in the range. */ + OutputIt out; + /** Sentinel indicating the end of the output range. */ + OutputSen out_last; + + FMT_CONSTEXPR operator OutputIt&() & noexcept { return out; } + FMT_CONSTEXPR operator const OutputIt&() const& noexcept { return out; } + FMT_CONSTEXPR operator OutputIt&&() && noexcept { + return static_cast(out); + } +}; + +template +auto vformat_to(char (&out)[Size], string_view fmt, format_args args) + -> format_to_result { + format_to_n_result result = vformat_to_n(out, Size, fmt, args); + return {result.out, out + Size}; +} + +template +FMT_INLINE auto format_to(char (&out)[Size], format_string fmt, + T&&... args) -> format_to_result { + return vformat_to(out, fmt, fmt::make_format_args(args...)); +} + /** Returns the number of chars in the output of ``format(fmt, args...)``. */ template FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, diff --git a/test/base-test.cc b/test/base-test.cc index f7365637..1a9776c8 100644 --- a/test/base-test.cc +++ b/test/base-test.cc @@ -9,15 +9,16 @@ #include "test-assert.h" // clang-format on +#include "fmt/base.h" + #include // INT_MAX #include // std::strlen #include // std::equal_to -#include // std::back_insert_iterator +#include // std::back_insert_iterator, std::distance #include // std::numeric_limits #include // std::string #include // std::is_same -#include "fmt/base.h" #include "gmock/gmock.h" using fmt::string_view; @@ -692,6 +693,47 @@ TEST(core_test, format_to) { EXPECT_EQ(s, "42"); } +TEST(core_test, format_to_c_array) { + char buffer[4]; + auto result = fmt::format_to(buffer, "{}", 12345); + EXPECT_EQ(4, std::distance(&buffer[0], result.out)); + EXPECT_EQ(0, std::distance(result.out, result.out_last)); + EXPECT_EQ(buffer + 4, result.out); + EXPECT_EQ("1234", fmt::string_view(buffer, 4)); + + result = fmt::format_to(buffer, "{:s}", "foobar"); + EXPECT_EQ(4, std::distance(&buffer[0], result.out)); + EXPECT_EQ(0, std::distance(result.out, result.out_last)); + EXPECT_EQ(buffer + 4, result.out); + EXPECT_EQ("foob", fmt::string_view(buffer, 4)); + + buffer[0] = 'x'; + buffer[1] = 'x'; + buffer[2] = 'x'; + buffer[3] = 'x'; + result = fmt::format_to(buffer, "{}", 'A'); + EXPECT_EQ(1, std::distance(&buffer[0], result.out)); + EXPECT_EQ(3, std::distance(result.out, result.out_last)); + EXPECT_EQ(buffer + 1, result.out); + EXPECT_EQ("Axxx", fmt::string_view(buffer, 4)); + + result = fmt::format_to(buffer, "{}{} ", 'B', 'C'); + EXPECT_EQ(3, std::distance(&buffer[0], result.out)); + EXPECT_EQ(1, std::distance(result.out, result.out_last)); + EXPECT_EQ(buffer + 3, result.out); + EXPECT_EQ("BC x", fmt::string_view(buffer, 4)); + + result = fmt::format_to(buffer, "{}", "ABCDE"); + EXPECT_EQ(4, std::distance(&buffer[0], result.out)); + EXPECT_EQ(0, std::distance(result.out, result.out_last)); + EXPECT_EQ("ABCD", fmt::string_view(buffer, 4)); + + result = fmt::format_to(buffer, "{}", std::string(1000, '*')); + EXPECT_EQ(4, std::distance(&buffer[0], result.out)); + EXPECT_EQ(0, std::distance(result.out, result.out_last)); + EXPECT_EQ("****", fmt::string_view(buffer, 4)); +} + #ifdef __cpp_lib_byte TEST(core_test, format_byte) { auto s = std::string();