Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🛠 Add basic array safety functions and backwards-compatible result type #3805

Merged
merged 4 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions include/fmt/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@
# define FMT_UNICODE !FMT_MSC_VERSION
#endif

#define FMT_FWD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)

// Enable minimal optimizations for more compact code in debug mode.
FMT_GCC_PRAGMA("GCC push_options")
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__)
Expand Down Expand Up @@ -2811,8 +2813,10 @@ inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; }

/** Formats a string and writes the output to ``out``. */
template <typename OutputIt,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt {
FMT_ENABLE_IF(detail::is_output_iterator<remove_cvref_t<OutputIt>,
char>::value)>
auto vformat_to(OutputIt&& out, string_view fmt, format_args args)
ThePhD marked this conversation as resolved.
Show resolved Hide resolved
-> remove_cvref_t<OutputIt> {
auto&& buf = detail::get_buffer<char>(out);
detail::vformat_to(buf, fmt, args, {});
return detail::get_iterator(buf, out);
Expand All @@ -2831,10 +2835,11 @@ auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt {
\endrst
*/
template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
FMT_INLINE auto format_to(OutputIt out, format_string<T...> fmt, T&&... args)
-> OutputIt {
return vformat_to(out, fmt, fmt::make_format_args(args...));
FMT_ENABLE_IF(detail::is_output_iterator<remove_cvref_t<OutputIt>,
char>::value)>
FMT_INLINE auto format_to(OutputIt&& out, format_string<T...> fmt, T&&... args)
-> remove_cvref_t<OutputIt> {
return vformat_to(FMT_FWD(out), fmt, fmt::make_format_args(args...));
}

template <typename OutputIt> struct format_to_n_result {
Expand Down Expand Up @@ -2869,6 +2874,33 @@ FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string<T...> fmt,
return vformat_to_n(out, n, fmt, fmt::make_format_args(args...));
}

template <typename OutputIt, typename OutputSen = OutputIt>
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<OutputIt&&>(out);
}
};

template <size_t Size>
auto vformat_to(char (&out)[Size], string_view fmt, format_args args)
-> format_to_result<char*> {
format_to_n_result<char*> result = vformat_to_n(out, Size, fmt, args);
return {result.out, out + Size};
}

template <size_t Size, typename... T>
FMT_INLINE auto format_to(char (&out)[Size], format_string<T...> fmt,
T&&... args) -> format_to_result<char*> {
return vformat_to(out, fmt, fmt::make_format_args(args...));
}

/** Returns the number of chars in the output of ``format(fmt, args...)``. */
template <typename... T>
FMT_NODISCARD FMT_INLINE auto formatted_size(format_string<T...> fmt,
Expand Down
46 changes: 44 additions & 2 deletions test/base-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
#include "test-assert.h"
// clang-format on

#include "fmt/base.h"

#include <climits> // INT_MAX
#include <cstring> // std::strlen
#include <functional> // std::equal_to
#include <iterator> // std::back_insert_iterator
#include <iterator> // std::back_insert_iterator, std::distance
#include <limits> // std::numeric_limits
#include <string> // std::string
#include <type_traits> // std::is_same

#include "fmt/base.h"
#include "gmock/gmock.h"

using fmt::string_view;
Expand Down Expand Up @@ -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();
Expand Down
Loading