Adds `absl::StringResizeAndOverwrite` as a polyfill for C++23's `std::basic_string<CharT,Traits,Allocator>::resize_and_overwrite` #1136 PiperOrigin-RevId: 815709814 Change-Id: Ie6b98d19058c5403fb3f6d65ccc82e2bb46ec4f6
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index ac23f25..4944823 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake
@@ -374,6 +374,7 @@ "strings/internal/str_split_internal.h" "strings/internal/utf8.cc" "strings/internal/utf8.h" + "strings/resize_and_overwrite.h" "synchronization/barrier.cc" "synchronization/barrier.h" "synchronization/blocking_counter.cc"
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 2e73f80..bcf0c38 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel
@@ -142,6 +142,31 @@ ], ) +cc_library( + name = "resize_and_overwrite", + hdrs = ["resize_and_overwrite.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:throw_delegate", + ], +) + +cc_test( + name = "resize_and_overwrite_test", + srcs = ["resize_and_overwrite_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":resize_and_overwrite", + "//absl/log:absl_check", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + cc_test( name = "match_test", size = "small", @@ -1095,12 +1120,12 @@ name = "resize_uninitialized_test", size = "small", srcs = [ - "internal/resize_uninitialized.h", "internal/resize_uninitialized_test.cc", ], copts = ABSL_TEST_COPTS, visibility = ["//visibility:private"], deps = [ + ":internal", "//absl/base:core_headers", "//absl/meta:type_traits", "@googletest//:gtest",
diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 32ad263..764d5cd 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt
@@ -141,6 +141,32 @@ absl::type_traits ) +absl_cc_library( + NAME + strings_resize_and_overwrite + HDRS + "resize_and_overwrite.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + absl::core_headers + absl::throw_delegate +) + +absl_cc_test( + NAME + strings_resize_and_overwrite_test + SRCS + "resize_and_overwrite_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::strings_resize_and_overwrite + absl::absl_check + GTest::gmock_main +) + absl_cc_test( NAME match_test @@ -337,11 +363,11 @@ NAME resize_uninitialized_test SRCS - "internal/resize_uninitialized.h" "internal/resize_uninitialized_test.cc" COPTS ${ABSL_TEST_COPTS} DEPS + absl::strings_internal absl::base absl::core_headers absl::type_traits
diff --git a/absl/strings/resize_and_overwrite.h b/absl/strings/resize_and_overwrite.h new file mode 100644 index 0000000..7d07640 --- /dev/null +++ b/absl/strings/resize_and_overwrite.h
@@ -0,0 +1,172 @@ +// Copyright 2025 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: resize_and_overwrite.h +// ----------------------------------------------------------------------------- +// +// This file contains a polyfill for C++23's +// std::basic_string<CharT,Traits,Allocator>::resize_and_overwrite +// +// The polyfill takes the form of a free function: + +// template<typename T, typename Op> +// void StringResizeAndOverwrite(T& str, typename T::size_type count, Op op); +// +// This avoids the cost of initializing a suitably-sized std::string when it is +// intended to be used as a char array, for example, to be populated by a +// C-style API. +// +// Example usage: +// +// std::string IntToString(int n) { +// std::string result; +// constexpr size_t kMaxIntChars = 10; +// absl::StringResizeAndOverwrite( +// result, kMaxIntChars, [n](char* buffer, size_t buffer_size) { +// return snprintf(buffer, buffer_size, "%d", n); +// }); +// return result; +// } +// +// https://en.cppreference.com/w/cpp/string/basic_string/resize_and_overwrite.html +// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1072r10.html + +#ifndef ABSL_STRINGS_RESIZE_AND_OVERWRITE_H_ +#define ABSL_STRINGS_RESIZE_AND_OVERWRITE_H_ + +#include <cstddef> +#include <string> // IWYU pragma: keep +#include <type_traits> +#include <utility> + +#include "absl/base/config.h" +#include "absl/base/internal/throw_delegate.h" +#include "absl/base/macros.h" +#include "absl/base/optimization.h" + +#if defined(__cpp_lib_string_resize_and_overwrite) && \ + __cpp_lib_string_resize_and_overwrite >= 202110L +#define ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE 1 +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace strings_internal { + +#ifndef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE + +inline size_t ProbeResizeAndOverwriteOp(char*, size_t) { return 0; } + +// Prior to C++23, Google's libc++ backports resize_and_overwrite as +// __google_nonstandard_backport_resize_and_overwrite +template <typename T, typename = void> +struct has__google_nonstandard_backport_resize_and_overwrite : std::false_type { +}; + +template <typename T> +struct has__google_nonstandard_backport_resize_and_overwrite< + T, + std::void_t< + decltype(std::declval<T&>() + .__google_nonstandard_backport_resize_and_overwrite( + std::declval<size_t>(), ProbeResizeAndOverwriteOp))>> + : std::true_type {}; + +// Prior to C++23, the version of libstdc++ that shipped with GCC >= 14 +// has __resize_and_overwrite. +template <typename T, typename = void> +struct has__resize_and_overwrite : std::false_type {}; + +template <typename T> +struct has__resize_and_overwrite< + T, std::void_t<decltype(std::declval<T&>().__resize_and_overwrite( + std::declval<size_t>(), ProbeResizeAndOverwriteOp))>> + : std::true_type {}; + +// libc++ used __resize_default_init to achieve uninitialized string resizes +// before removing it September 2025, in favor of resize_and_overwrite. +// https://github.com/llvm/llvm-project/commit/92f5d8df361bb1bb6dea88f86faeedfd295ab970 +template <typename T, typename = void> +struct has__resize_default_init : std::false_type {}; + +template <typename T> +struct has__resize_default_init< + T, std::void_t<decltype(std::declval<T&>().__resize_default_init(42))>> + : std::true_type {}; + +// Prior to C++23, some versions of MSVC have _Resize_and_overwrite. +template <typename T, typename = void> +struct has_Resize_and_overwrite : std::false_type {}; + +template <typename T> +struct has_Resize_and_overwrite< + T, std::void_t<decltype(std::declval<T&>()._Resize_and_overwrite( + std::declval<size_t>(), ProbeResizeAndOverwriteOp))>> + : std::true_type {}; + +#endif // ifndef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE + +// A less-efficient fallback implementation that uses resize(). +template <typename T, typename Op> +void StringResizeAndOverwriteFallback(T& str, typename T::size_type n, Op op) { + if (ABSL_PREDICT_FALSE(n > str.max_size())) { + absl::base_internal::ThrowStdLengthError("absl::StringResizeAndOverwrite"); + } + // The callback is allowed to write an arbitrary value to buf+n, but it is + // undefined behavior to write anything other than T::value_type{} to + // str.data()[n]. Therefore the initial resize uses an extra byte. + str.resize(n + 1); + auto new_size = std::move(op)(str.data(), n); + ABSL_HARDENING_ASSERT(new_size >= 0 && new_size <= n); + str.erase(static_cast<typename T::size_type>(new_size)); +} + +} // namespace strings_internal + +// Resizes `str` to contain at most `n` characters, using the user-provided +// operation `op` to modify the possibly indeterminate contents. `op` must +// return the finalized length of `str`. Note that `op` is allowed write to +// `data()[n]`, which facilitiates interoperation with functions that write a +// trailing NUL. +template <typename T, typename Op> +void StringResizeAndOverwrite(T& str, typename T::size_type n, Op op) { +#ifdef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE + str.resize_and_overwrite(n, std::move(op)); +#else + if constexpr (strings_internal:: + has__google_nonstandard_backport_resize_and_overwrite< + T>::value) { + str.__google_nonstandard_backport_resize_and_overwrite(n, std::move(op)); + } else if constexpr (strings_internal::has__resize_and_overwrite<T>::value) { + str.__resize_and_overwrite(n, std::move(op)); + } else if constexpr (strings_internal::has__resize_default_init<T>::value) { + str.__resize_default_init(n); + str.__resize_default_init( + static_cast<typename T::size_type>(std::move(op)(str.data(), n))); + } else if constexpr (strings_internal::has_Resize_and_overwrite<T>::value) { + str._Resize_and_overwrite(n, std::move(op)); + } else { + strings_internal::StringResizeAndOverwriteFallback(str, n, op); + } +#endif +} + +ABSL_NAMESPACE_END +} // namespace absl + +#undef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE + +#endif // ABSL_STRINGS_RESIZE_AND_OVERWRITE_H_
diff --git a/absl/strings/resize_and_overwrite_test.cc b/absl/strings/resize_and_overwrite_test.cc new file mode 100644 index 0000000..ad4cca9 --- /dev/null +++ b/absl/strings/resize_and_overwrite_test.cc
@@ -0,0 +1,119 @@ +// Copyright 2025 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/resize_and_overwrite.h" + +#include <algorithm> +#include <cstddef> +#include <string> + +#include "gtest/gtest.h" +#include "absl/log/absl_check.h" + +namespace { + +struct ResizeAndOverwriteParam { + size_t initial_size; + size_t requested_capacity; + size_t final_size; +}; + +using StringResizeAndOverwriteTest = + ::testing::TestWithParam<ResizeAndOverwriteParam>; + +TEST_P(StringResizeAndOverwriteTest, StringResizeAndOverwrite) { + const auto& param = GetParam(); + std::string s(param.initial_size, 'a'); + absl::StringResizeAndOverwrite( + s, param.requested_capacity, [&](char* p, size_t n) { + ABSL_CHECK_EQ(n, param.requested_capacity); + if (param.final_size >= param.initial_size) { + // Append case. + std::fill(p + param.initial_size, p + param.final_size, 'b'); + } else if (param.final_size > 0) { + // Truncate case. + p[param.final_size - 1] = 'b'; + } + p[param.final_size] = 'c'; // Should be overwritten with '\0'; + return param.final_size; + }); + + std::string expected; + if (param.final_size >= param.initial_size) { + // Append case. + expected = std::string(param.initial_size, 'a') + + std::string(param.final_size - param.initial_size, 'b'); + } else if (param.final_size > 0) { + // Truncate case. + expected = std::string(param.final_size - 1, 'a') + std::string("b"); + } + + EXPECT_EQ(s, expected); + EXPECT_EQ(s.c_str()[param.final_size], '\0'); +} + +TEST_P(StringResizeAndOverwriteTest, StringResizeAndOverwriteFallback) { + const auto& param = GetParam(); + std::string s(param.initial_size, 'a'); + absl::strings_internal::StringResizeAndOverwriteFallback( + s, param.requested_capacity, [&](char* p, size_t n) { + ABSL_CHECK_EQ(n, param.requested_capacity); + if (param.final_size >= param.initial_size) { + // Append case. + std::fill(p + param.initial_size, p + param.final_size, 'b'); + } else if (param.final_size > 0) { + // Truncate case. + p[param.final_size - 1] = 'b'; + } + p[param.final_size] = 'c'; // Should be overwritten with '\0'; + return param.final_size; + }); + + std::string expected; + if (param.final_size >= param.initial_size) { + // Append case. + expected = std::string(param.initial_size, 'a') + + std::string(param.final_size - param.initial_size, 'b'); + } else if (param.final_size > 0) { + // Truncate case. + expected = std::string(param.final_size - 1, 'a') + std::string("b"); + } + + EXPECT_EQ(s, expected); + EXPECT_EQ(s.c_str()[param.final_size], '\0'); +} + +// clang-format off +INSTANTIATE_TEST_SUITE_P(StringResizeAndOverwriteTestSuite, + StringResizeAndOverwriteTest, + ::testing::ValuesIn<ResizeAndOverwriteParam>({ + // Append cases. + {0, 10, 5}, + {10, 10, 10}, + {10, 15, 15}, + {10, 20, 15}, + {10, 40, 40}, + {10, 50, 40}, + {30, 35, 35}, + {30, 45, 35}, + {10, 30, 15}, + // Truncate cases. + {15, 15, 10}, + {40, 40, 35}, + {40, 30, 10}, + {10, 15, 0}, + })); +// clang-format on + +} // namespace