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