mirror of
https://github.com/abseil/abseil-cpp.git
synced 2026-06-04 12:07:05 +08:00
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
This commit is contained in:
committed by
Copybara-Service
parent
1420ad8525
commit
0cf55c4fd6
@@ -374,6 +374,7 @@ set(ABSL_INTERNAL_DLL_FILES
|
||||
"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"
|
||||
|
||||
@@ -142,6 +142,31 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
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 @@ cc_test(
|
||||
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",
|
||||
|
||||
@@ -141,6 +141,32 @@ absl_cc_library(
|
||||
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 @@ absl_cc_test(
|
||||
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
|
||||
|
||||
172
absl/strings/resize_and_overwrite.h
Normal file
172
absl/strings/resize_and_overwrite.h
Normal file
@@ -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_
|
||||
119
absl/strings/resize_and_overwrite_test.cc
Normal file
119
absl/strings/resize_and_overwrite_test.cc
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user