Skip to content

Commit 0cf55c4

Browse files
derekmaurocopybara-github
authored andcommitted
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
1 parent 1420ad8 commit 0cf55c4

File tree

5 files changed

+345
-2
lines changed

5 files changed

+345
-2
lines changed

CMake/AbseilDll.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ set(ABSL_INTERNAL_DLL_FILES
374374
"strings/internal/str_split_internal.h"
375375
"strings/internal/utf8.cc"
376376
"strings/internal/utf8.h"
377+
"strings/resize_and_overwrite.h"
377378
"synchronization/barrier.cc"
378379
"synchronization/barrier.h"
379380
"synchronization/blocking_counter.cc"

absl/strings/BUILD.bazel

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,31 @@ cc_library(
142142
],
143143
)
144144

145+
cc_library(
146+
name = "resize_and_overwrite",
147+
hdrs = ["resize_and_overwrite.h"],
148+
copts = ABSL_DEFAULT_COPTS,
149+
linkopts = ABSL_DEFAULT_LINKOPTS,
150+
deps = [
151+
"//absl/base:config",
152+
"//absl/base:core_headers",
153+
"//absl/base:throw_delegate",
154+
],
155+
)
156+
157+
cc_test(
158+
name = "resize_and_overwrite_test",
159+
srcs = ["resize_and_overwrite_test.cc"],
160+
copts = ABSL_TEST_COPTS,
161+
linkopts = ABSL_DEFAULT_LINKOPTS,
162+
deps = [
163+
":resize_and_overwrite",
164+
"//absl/log:absl_check",
165+
"@googletest//:gtest",
166+
"@googletest//:gtest_main",
167+
],
168+
)
169+
145170
cc_test(
146171
name = "match_test",
147172
size = "small",
@@ -1095,12 +1120,12 @@ cc_test(
10951120
name = "resize_uninitialized_test",
10961121
size = "small",
10971122
srcs = [
1098-
"internal/resize_uninitialized.h",
10991123
"internal/resize_uninitialized_test.cc",
11001124
],
11011125
copts = ABSL_TEST_COPTS,
11021126
visibility = ["//visibility:private"],
11031127
deps = [
1128+
":internal",
11041129
"//absl/base:core_headers",
11051130
"//absl/meta:type_traits",
11061131
"@googletest//:gtest",

absl/strings/CMakeLists.txt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,32 @@ absl_cc_library(
141141
absl::type_traits
142142
)
143143

144+
absl_cc_library(
145+
NAME
146+
strings_resize_and_overwrite
147+
HDRS
148+
"resize_and_overwrite.h"
149+
COPTS
150+
${ABSL_DEFAULT_COPTS}
151+
DEPS
152+
absl::config
153+
absl::core_headers
154+
absl::throw_delegate
155+
)
156+
157+
absl_cc_test(
158+
NAME
159+
strings_resize_and_overwrite_test
160+
SRCS
161+
"resize_and_overwrite_test.cc"
162+
COPTS
163+
${ABSL_TEST_COPTS}
164+
DEPS
165+
absl::strings_resize_and_overwrite
166+
absl::absl_check
167+
GTest::gmock_main
168+
)
169+
144170
absl_cc_test(
145171
NAME
146172
match_test
@@ -337,11 +363,11 @@ absl_cc_test(
337363
NAME
338364
resize_uninitialized_test
339365
SRCS
340-
"internal/resize_uninitialized.h"
341366
"internal/resize_uninitialized_test.cc"
342367
COPTS
343368
${ABSL_TEST_COPTS}
344369
DEPS
370+
absl::strings_internal
345371
absl::base
346372
absl::core_headers
347373
absl::type_traits
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Copyright 2025 The Abseil Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// -----------------------------------------------------------------------------
16+
// File: resize_and_overwrite.h
17+
// -----------------------------------------------------------------------------
18+
//
19+
// This file contains a polyfill for C++23's
20+
// std::basic_string<CharT,Traits,Allocator>::resize_and_overwrite
21+
//
22+
// The polyfill takes the form of a free function:
23+
24+
// template<typename T, typename Op>
25+
// void StringResizeAndOverwrite(T& str, typename T::size_type count, Op op);
26+
//
27+
// This avoids the cost of initializing a suitably-sized std::string when it is
28+
// intended to be used as a char array, for example, to be populated by a
29+
// C-style API.
30+
//
31+
// Example usage:
32+
//
33+
// std::string IntToString(int n) {
34+
// std::string result;
35+
// constexpr size_t kMaxIntChars = 10;
36+
// absl::StringResizeAndOverwrite(
37+
// result, kMaxIntChars, [n](char* buffer, size_t buffer_size) {
38+
// return snprintf(buffer, buffer_size, "%d", n);
39+
// });
40+
// return result;
41+
// }
42+
//
43+
// https://en.cppreference.com/w/cpp/string/basic_string/resize_and_overwrite.html
44+
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1072r10.html
45+
46+
#ifndef ABSL_STRINGS_RESIZE_AND_OVERWRITE_H_
47+
#define ABSL_STRINGS_RESIZE_AND_OVERWRITE_H_
48+
49+
#include <cstddef>
50+
#include <string> // IWYU pragma: keep
51+
#include <type_traits>
52+
#include <utility>
53+
54+
#include "absl/base/config.h"
55+
#include "absl/base/internal/throw_delegate.h"
56+
#include "absl/base/macros.h"
57+
#include "absl/base/optimization.h"
58+
59+
#if defined(__cpp_lib_string_resize_and_overwrite) && \
60+
__cpp_lib_string_resize_and_overwrite >= 202110L
61+
#define ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE 1
62+
#endif
63+
64+
namespace absl {
65+
ABSL_NAMESPACE_BEGIN
66+
67+
namespace strings_internal {
68+
69+
#ifndef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE
70+
71+
inline size_t ProbeResizeAndOverwriteOp(char*, size_t) { return 0; }
72+
73+
// Prior to C++23, Google's libc++ backports resize_and_overwrite as
74+
// __google_nonstandard_backport_resize_and_overwrite
75+
template <typename T, typename = void>
76+
struct has__google_nonstandard_backport_resize_and_overwrite : std::false_type {
77+
};
78+
79+
template <typename T>
80+
struct has__google_nonstandard_backport_resize_and_overwrite<
81+
T,
82+
std::void_t<
83+
decltype(std::declval<T&>()
84+
.__google_nonstandard_backport_resize_and_overwrite(
85+
std::declval<size_t>(), ProbeResizeAndOverwriteOp))>>
86+
: std::true_type {};
87+
88+
// Prior to C++23, the version of libstdc++ that shipped with GCC >= 14
89+
// has __resize_and_overwrite.
90+
template <typename T, typename = void>
91+
struct has__resize_and_overwrite : std::false_type {};
92+
93+
template <typename T>
94+
struct has__resize_and_overwrite<
95+
T, std::void_t<decltype(std::declval<T&>().__resize_and_overwrite(
96+
std::declval<size_t>(), ProbeResizeAndOverwriteOp))>>
97+
: std::true_type {};
98+
99+
// libc++ used __resize_default_init to achieve uninitialized string resizes
100+
// before removing it September 2025, in favor of resize_and_overwrite.
101+
// https://github.com/llvm/llvm-project/commit/92f5d8df361bb1bb6dea88f86faeedfd295ab970
102+
template <typename T, typename = void>
103+
struct has__resize_default_init : std::false_type {};
104+
105+
template <typename T>
106+
struct has__resize_default_init<
107+
T, std::void_t<decltype(std::declval<T&>().__resize_default_init(42))>>
108+
: std::true_type {};
109+
110+
// Prior to C++23, some versions of MSVC have _Resize_and_overwrite.
111+
template <typename T, typename = void>
112+
struct has_Resize_and_overwrite : std::false_type {};
113+
114+
template <typename T>
115+
struct has_Resize_and_overwrite<
116+
T, std::void_t<decltype(std::declval<T&>()._Resize_and_overwrite(
117+
std::declval<size_t>(), ProbeResizeAndOverwriteOp))>>
118+
: std::true_type {};
119+
120+
#endif // ifndef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE
121+
122+
// A less-efficient fallback implementation that uses resize().
123+
template <typename T, typename Op>
124+
void StringResizeAndOverwriteFallback(T& str, typename T::size_type n, Op op) {
125+
if (ABSL_PREDICT_FALSE(n > str.max_size())) {
126+
absl::base_internal::ThrowStdLengthError("absl::StringResizeAndOverwrite");
127+
}
128+
// The callback is allowed to write an arbitrary value to buf+n, but it is
129+
// undefined behavior to write anything other than T::value_type{} to
130+
// str.data()[n]. Therefore the initial resize uses an extra byte.
131+
str.resize(n + 1);
132+
auto new_size = std::move(op)(str.data(), n);
133+
ABSL_HARDENING_ASSERT(new_size >= 0 && new_size <= n);
134+
str.erase(static_cast<typename T::size_type>(new_size));
135+
}
136+
137+
} // namespace strings_internal
138+
139+
// Resizes `str` to contain at most `n` characters, using the user-provided
140+
// operation `op` to modify the possibly indeterminate contents. `op` must
141+
// return the finalized length of `str`. Note that `op` is allowed write to
142+
// `data()[n]`, which facilitiates interoperation with functions that write a
143+
// trailing NUL.
144+
template <typename T, typename Op>
145+
void StringResizeAndOverwrite(T& str, typename T::size_type n, Op op) {
146+
#ifdef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE
147+
str.resize_and_overwrite(n, std::move(op));
148+
#else
149+
if constexpr (strings_internal::
150+
has__google_nonstandard_backport_resize_and_overwrite<
151+
T>::value) {
152+
str.__google_nonstandard_backport_resize_and_overwrite(n, std::move(op));
153+
} else if constexpr (strings_internal::has__resize_and_overwrite<T>::value) {
154+
str.__resize_and_overwrite(n, std::move(op));
155+
} else if constexpr (strings_internal::has__resize_default_init<T>::value) {
156+
str.__resize_default_init(n);
157+
str.__resize_default_init(
158+
static_cast<typename T::size_type>(std::move(op)(str.data(), n)));
159+
} else if constexpr (strings_internal::has_Resize_and_overwrite<T>::value) {
160+
str._Resize_and_overwrite(n, std::move(op));
161+
} else {
162+
strings_internal::StringResizeAndOverwriteFallback(str, n, op);
163+
}
164+
#endif
165+
}
166+
167+
ABSL_NAMESPACE_END
168+
} // namespace absl
169+
170+
#undef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE
171+
172+
#endif // ABSL_STRINGS_RESIZE_AND_OVERWRITE_H_
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2025 The Abseil Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "absl/strings/resize_and_overwrite.h"
16+
17+
#include <algorithm>
18+
#include <cstddef>
19+
#include <string>
20+
21+
#include "gtest/gtest.h"
22+
#include "absl/log/absl_check.h"
23+
24+
namespace {
25+
26+
struct ResizeAndOverwriteParam {
27+
size_t initial_size;
28+
size_t requested_capacity;
29+
size_t final_size;
30+
};
31+
32+
using StringResizeAndOverwriteTest =
33+
::testing::TestWithParam<ResizeAndOverwriteParam>;
34+
35+
TEST_P(StringResizeAndOverwriteTest, StringResizeAndOverwrite) {
36+
const auto& param = GetParam();
37+
std::string s(param.initial_size, 'a');
38+
absl::StringResizeAndOverwrite(
39+
s, param.requested_capacity, [&](char* p, size_t n) {
40+
ABSL_CHECK_EQ(n, param.requested_capacity);
41+
if (param.final_size >= param.initial_size) {
42+
// Append case.
43+
std::fill(p + param.initial_size, p + param.final_size, 'b');
44+
} else if (param.final_size > 0) {
45+
// Truncate case.
46+
p[param.final_size - 1] = 'b';
47+
}
48+
p[param.final_size] = 'c'; // Should be overwritten with '\0';
49+
return param.final_size;
50+
});
51+
52+
std::string expected;
53+
if (param.final_size >= param.initial_size) {
54+
// Append case.
55+
expected = std::string(param.initial_size, 'a') +
56+
std::string(param.final_size - param.initial_size, 'b');
57+
} else if (param.final_size > 0) {
58+
// Truncate case.
59+
expected = std::string(param.final_size - 1, 'a') + std::string("b");
60+
}
61+
62+
EXPECT_EQ(s, expected);
63+
EXPECT_EQ(s.c_str()[param.final_size], '\0');
64+
}
65+
66+
TEST_P(StringResizeAndOverwriteTest, StringResizeAndOverwriteFallback) {
67+
const auto& param = GetParam();
68+
std::string s(param.initial_size, 'a');
69+
absl::strings_internal::StringResizeAndOverwriteFallback(
70+
s, param.requested_capacity, [&](char* p, size_t n) {
71+
ABSL_CHECK_EQ(n, param.requested_capacity);
72+
if (param.final_size >= param.initial_size) {
73+
// Append case.
74+
std::fill(p + param.initial_size, p + param.final_size, 'b');
75+
} else if (param.final_size > 0) {
76+
// Truncate case.
77+
p[param.final_size - 1] = 'b';
78+
}
79+
p[param.final_size] = 'c'; // Should be overwritten with '\0';
80+
return param.final_size;
81+
});
82+
83+
std::string expected;
84+
if (param.final_size >= param.initial_size) {
85+
// Append case.
86+
expected = std::string(param.initial_size, 'a') +
87+
std::string(param.final_size - param.initial_size, 'b');
88+
} else if (param.final_size > 0) {
89+
// Truncate case.
90+
expected = std::string(param.final_size - 1, 'a') + std::string("b");
91+
}
92+
93+
EXPECT_EQ(s, expected);
94+
EXPECT_EQ(s.c_str()[param.final_size], '\0');
95+
}
96+
97+
// clang-format off
98+
INSTANTIATE_TEST_SUITE_P(StringResizeAndOverwriteTestSuite,
99+
StringResizeAndOverwriteTest,
100+
::testing::ValuesIn<ResizeAndOverwriteParam>({
101+
// Append cases.
102+
{0, 10, 5},
103+
{10, 10, 10},
104+
{10, 15, 15},
105+
{10, 20, 15},
106+
{10, 40, 40},
107+
{10, 50, 40},
108+
{30, 35, 35},
109+
{30, 45, 35},
110+
{10, 30, 15},
111+
// Truncate cases.
112+
{15, 15, 10},
113+
{40, 40, 35},
114+
{40, 30, 10},
115+
{10, 15, 0},
116+
}));
117+
// clang-format on
118+
119+
} // namespace

0 commit comments

Comments
 (0)