mirror of
https://github.com/abseil/abseil-cpp.git
synced 2026-06-04 12:07:05 +08:00
Remove the implementation of absl::string_view, which was only needed
prior to C++17. `absl::string_view` is now an alias for `std::string_view`. It is recommended that clients simply use `std::string_view`. PiperOrigin-RevId: 845822478 Change-Id: I220530c84118e5b9ef110baa002c232ac8f2c5f2
This commit is contained in:
committed by
Copybara-Service
parent
d2dd9b9ee9
commit
9ebd93a774
@@ -345,8 +345,6 @@ set(ABSL_INTERNAL_DLL_FILES
|
||||
"strings/str_replace.h"
|
||||
"strings/str_split.cc"
|
||||
"strings/str_split.h"
|
||||
"strings/string_view.cc"
|
||||
"strings/string_view.h"
|
||||
"strings/strip.h"
|
||||
"strings/substitute.cc"
|
||||
"strings/substitute.h"
|
||||
@@ -446,6 +444,7 @@ set(ABSL_INTERNAL_DLL_FILES
|
||||
"types/variant.h"
|
||||
"utility/utility.h"
|
||||
"debugging/leak_check.cc"
|
||||
"strings/string_view.h"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
|
||||
@@ -526,20 +526,11 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' ||
|
||||
#define ABSL_USES_STD_ANY 1
|
||||
#define ABSL_HAVE_STD_OPTIONAL 1
|
||||
#define ABSL_USES_STD_OPTIONAL 1
|
||||
#define ABSL_HAVE_STD_STRING_VIEW 1
|
||||
#define ABSL_USES_STD_STRING_VIEW 1
|
||||
#define ABSL_HAVE_STD_VARIANT 1
|
||||
#define ABSL_USES_STD_VARIANT 1
|
||||
|
||||
// ABSL_HAVE_STD_STRING_VIEW
|
||||
//
|
||||
// Deprecated: always defined to 1.
|
||||
// std::string_view was added in C++17, which means all versions of C++
|
||||
// supported by Abseil have it.
|
||||
#ifdef ABSL_HAVE_STD_STRING_VIEW
|
||||
#error "ABSL_HAVE_STD_STRING_VIEW cannot be directly set."
|
||||
#else
|
||||
#define ABSL_HAVE_STD_STRING_VIEW 1
|
||||
#endif
|
||||
|
||||
// ABSL_HAVE_STD_ORDERING
|
||||
//
|
||||
// Checks whether C++20 std::{partial,weak,strong}_ordering are available.
|
||||
@@ -556,20 +547,6 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' ||
|
||||
#define ABSL_HAVE_STD_ORDERING 1
|
||||
#endif
|
||||
|
||||
// ABSL_USES_STD_STRING_VIEW
|
||||
//
|
||||
// Indicates whether absl::string_view is an alias for std::string_view.
|
||||
#if !defined(ABSL_OPTION_USE_STD_STRING_VIEW)
|
||||
#error options.h is misconfigured.
|
||||
#elif ABSL_OPTION_USE_STD_STRING_VIEW == 0
|
||||
#undef ABSL_USES_STD_STRING_VIEW
|
||||
#elif ABSL_OPTION_USE_STD_STRING_VIEW == 1 || \
|
||||
ABSL_OPTION_USE_STD_STRING_VIEW == 2
|
||||
#define ABSL_USES_STD_STRING_VIEW 1
|
||||
#else
|
||||
#error options.h is misconfigured.
|
||||
#endif
|
||||
|
||||
// ABSL_USES_STD_ORDERING
|
||||
//
|
||||
// Indicates whether absl::{partial,weak,strong}_ordering are aliases for the
|
||||
|
||||
@@ -73,32 +73,6 @@
|
||||
// Type Compatibility Options
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// ABSL_OPTION_USE_STD_STRING_VIEW
|
||||
//
|
||||
// This option controls whether absl::string_view is implemented as an alias to
|
||||
// std::string_view, or as an independent implementation.
|
||||
//
|
||||
// A value of 0 means to use Abseil's implementation. This requires only C++11
|
||||
// support, and is expected to work on every toolchain we support.
|
||||
//
|
||||
// A value of 1 means to use an alias to std::string_view. This requires that
|
||||
// all code using Abseil is built in C++17 mode or later.
|
||||
//
|
||||
// A value of 2 means to detect the C++ version being used to compile Abseil,
|
||||
// and use an alias only if a working std::string_view is available. This
|
||||
// option is useful when you are building your program from source. It should
|
||||
// not be used otherwise -- for example, if you are distributing Abseil in a
|
||||
// binary package manager -- since in mode 2, absl::string_view will name a
|
||||
// different type, with a different mangled name and binary layout, depending on
|
||||
// the compiler flags passed by the end user. For more info, see
|
||||
// https://abseil.io/about/design/dropin-types.
|
||||
//
|
||||
// User code should not inspect this macro. To check in the preprocessor if
|
||||
// absl::string_view is a typedef of std::string_view, use the feature macro
|
||||
// ABSL_USES_STD_STRING_VIEW.
|
||||
|
||||
#define ABSL_OPTION_USE_STD_STRING_VIEW 2
|
||||
|
||||
// ABSL_OPTION_USE_STD_ORDERING
|
||||
//
|
||||
// This option controls whether absl::{partial,weak,strong}_ordering are
|
||||
|
||||
@@ -36,16 +36,13 @@ licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "string_view",
|
||||
srcs = ["string_view.cc"],
|
||||
hdrs = ["string_view.h"],
|
||||
copts = ABSL_DEFAULT_COPTS,
|
||||
linkopts = ABSL_DEFAULT_LINKOPTS,
|
||||
deps = [
|
||||
"//absl/base",
|
||||
"//absl/base:config",
|
||||
"//absl/base:core_headers",
|
||||
"//absl/base:nullability",
|
||||
"//absl/base:throw_delegate",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -410,23 +407,6 @@ cc_test(
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "string_view_benchmark",
|
||||
testonly = True,
|
||||
srcs = ["string_view_benchmark.cc"],
|
||||
copts = ABSL_TEST_COPTS,
|
||||
tags = ["benchmark"],
|
||||
visibility = ["//visibility:private"],
|
||||
deps = [
|
||||
":string_view",
|
||||
":strings",
|
||||
"//absl/base:core_headers",
|
||||
"//absl/base:raw_logging_internal",
|
||||
"//absl/random",
|
||||
"@google_benchmark//:benchmark_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "string_view_test",
|
||||
size = "small",
|
||||
|
||||
@@ -19,16 +19,12 @@ absl_cc_library(
|
||||
string_view
|
||||
HDRS
|
||||
"string_view.h"
|
||||
SRCS
|
||||
"string_view.cc"
|
||||
COPTS
|
||||
${ABSL_DEFAULT_COPTS}
|
||||
DEPS
|
||||
absl::base
|
||||
absl::config
|
||||
absl::core_headers
|
||||
absl::nullability
|
||||
absl::throw_delegate
|
||||
PUBLIC
|
||||
)
|
||||
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
// Copyright 2017 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/string_view.h"
|
||||
|
||||
#ifndef ABSL_USES_STD_STRING_VIEW
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
#include <ostream>
|
||||
|
||||
#include "absl/base/nullability.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
|
||||
namespace {
|
||||
|
||||
// This is significantly faster for case-sensitive matches with very
|
||||
// few possible matches.
|
||||
const char* absl_nullable memmatch(const char* absl_nullable phaystack,
|
||||
size_t haylen,
|
||||
const char* absl_nullable pneedle,
|
||||
size_t neelen) {
|
||||
if (0 == neelen) {
|
||||
return phaystack; // even if haylen is 0
|
||||
}
|
||||
if (haylen < neelen) return nullptr;
|
||||
|
||||
const char* match;
|
||||
const char* hayend = phaystack + haylen - neelen + 1;
|
||||
// A static cast is used here as memchr returns a const void *, and pointer
|
||||
// arithmetic is not allowed on pointers to void.
|
||||
while (
|
||||
(match = static_cast<const char*>(memchr(
|
||||
phaystack, pneedle[0], static_cast<size_t>(hayend - phaystack))))) {
|
||||
if (memcmp(match, pneedle, neelen) == 0)
|
||||
return match;
|
||||
else
|
||||
phaystack = match + 1;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void WritePadding(std::ostream& o, size_t pad) {
|
||||
char fill_buf[32];
|
||||
memset(fill_buf, o.fill(), sizeof(fill_buf));
|
||||
while (pad) {
|
||||
size_t n = std::min(pad, sizeof(fill_buf));
|
||||
o.write(fill_buf, static_cast<std::streamsize>(n));
|
||||
pad -= n;
|
||||
}
|
||||
}
|
||||
|
||||
class LookupTable {
|
||||
public:
|
||||
// For each character in wanted, sets the index corresponding
|
||||
// to the ASCII code of that character. This is used by
|
||||
// the find_.*_of methods below to tell whether or not a character is in
|
||||
// the lookup table in constant time.
|
||||
explicit LookupTable(string_view wanted) {
|
||||
for (char c : wanted) {
|
||||
table_[Index(c)] = true;
|
||||
}
|
||||
}
|
||||
bool operator[](char c) const { return table_[Index(c)]; }
|
||||
|
||||
private:
|
||||
static unsigned char Index(char c) { return static_cast<unsigned char>(c); }
|
||||
bool table_[UCHAR_MAX + 1] = {};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::ostream& operator<<(std::ostream& o, string_view piece) {
|
||||
std::ostream::sentry sentry(o);
|
||||
if (sentry) {
|
||||
size_t lpad = 0;
|
||||
size_t rpad = 0;
|
||||
if (static_cast<size_t>(o.width()) > piece.size()) {
|
||||
size_t pad = static_cast<size_t>(o.width()) - piece.size();
|
||||
if ((o.flags() & o.adjustfield) == o.left) {
|
||||
rpad = pad;
|
||||
} else {
|
||||
lpad = pad;
|
||||
}
|
||||
}
|
||||
if (lpad) WritePadding(o, lpad);
|
||||
o.write(piece.data(), static_cast<std::streamsize>(piece.size()));
|
||||
if (rpad) WritePadding(o, rpad);
|
||||
o.width(0);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
string_view::size_type string_view::find(string_view s,
|
||||
size_type pos) const noexcept {
|
||||
if (empty() || pos > length_) {
|
||||
if (empty() && pos == 0 && s.empty()) return 0;
|
||||
return npos;
|
||||
}
|
||||
const char* result = memmatch(ptr_ + pos, length_ - pos, s.ptr_, s.length_);
|
||||
return result ? static_cast<size_type>(result - ptr_) : npos;
|
||||
}
|
||||
|
||||
string_view::size_type string_view::find(char c, size_type pos) const noexcept {
|
||||
if (empty() || pos >= length_) {
|
||||
return npos;
|
||||
}
|
||||
const char* result =
|
||||
static_cast<const char*>(memchr(ptr_ + pos, c, length_ - pos));
|
||||
return result != nullptr ? static_cast<size_type>(result - ptr_) : npos;
|
||||
}
|
||||
|
||||
string_view::size_type string_view::rfind(string_view s,
|
||||
size_type pos) const noexcept {
|
||||
if (length_ < s.length_) return npos;
|
||||
if (s.empty()) return std::min(length_, pos);
|
||||
const char* last = ptr_ + std::min(length_ - s.length_, pos) + s.length_;
|
||||
const char* result = std::find_end(ptr_, last, s.ptr_, s.ptr_ + s.length_);
|
||||
return result != last ? static_cast<size_type>(result - ptr_) : npos;
|
||||
}
|
||||
|
||||
// Search range is [0..pos] inclusive. If pos == npos, search everything.
|
||||
string_view::size_type string_view::rfind(char c,
|
||||
size_type pos) const noexcept {
|
||||
// Note: memrchr() is not available on Windows.
|
||||
if (empty()) return npos;
|
||||
for (size_type i = std::min(pos, length_ - 1);; --i) {
|
||||
if (ptr_[i] == c) {
|
||||
return i;
|
||||
}
|
||||
if (i == 0) break;
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
string_view::size_type string_view::find_first_of(
|
||||
string_view s, size_type pos) const noexcept {
|
||||
if (empty() || s.empty()) {
|
||||
return npos;
|
||||
}
|
||||
// Avoid the cost of LookupTable() for a single-character search.
|
||||
if (s.length_ == 1) return find_first_of(s.ptr_[0], pos);
|
||||
LookupTable tbl(s);
|
||||
for (size_type i = pos; i < length_; ++i) {
|
||||
if (tbl[ptr_[i]]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
string_view::size_type string_view::find_first_not_of(
|
||||
string_view s, size_type pos) const noexcept {
|
||||
if (empty()) return npos;
|
||||
// Avoid the cost of LookupTable() for a single-character search.
|
||||
if (s.length_ == 1) return find_first_not_of(s.ptr_[0], pos);
|
||||
LookupTable tbl(s);
|
||||
for (size_type i = pos; i < length_; ++i) {
|
||||
if (!tbl[ptr_[i]]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
string_view::size_type string_view::find_first_not_of(
|
||||
char c, size_type pos) const noexcept {
|
||||
if (empty()) return npos;
|
||||
for (; pos < length_; ++pos) {
|
||||
if (ptr_[pos] != c) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
string_view::size_type string_view::find_last_of(string_view s,
|
||||
size_type pos) const noexcept {
|
||||
if (empty() || s.empty()) return npos;
|
||||
// Avoid the cost of LookupTable() for a single-character search.
|
||||
if (s.length_ == 1) return find_last_of(s.ptr_[0], pos);
|
||||
LookupTable tbl(s);
|
||||
for (size_type i = std::min(pos, length_ - 1);; --i) {
|
||||
if (tbl[ptr_[i]]) {
|
||||
return i;
|
||||
}
|
||||
if (i == 0) break;
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
string_view::size_type string_view::find_last_not_of(
|
||||
string_view s, size_type pos) const noexcept {
|
||||
if (empty()) return npos;
|
||||
size_type i = std::min(pos, length_ - 1);
|
||||
if (s.empty()) return i;
|
||||
// Avoid the cost of LookupTable() for a single-character search.
|
||||
if (s.length_ == 1) return find_last_not_of(s.ptr_[0], pos);
|
||||
LookupTable tbl(s);
|
||||
for (;; --i) {
|
||||
if (!tbl[ptr_[i]]) {
|
||||
return i;
|
||||
}
|
||||
if (i == 0) break;
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
string_view::size_type string_view::find_last_not_of(
|
||||
char c, size_type pos) const noexcept {
|
||||
if (empty()) return npos;
|
||||
size_type i = std::min(pos, length_ - 1);
|
||||
for (;; --i) {
|
||||
if (ptr_[i] != c) {
|
||||
return i;
|
||||
}
|
||||
if (i == 0) break;
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#else
|
||||
|
||||
// https://github.com/abseil/abseil-cpp/issues/1465
|
||||
// CMake builds on Apple platforms error when libraries are empty.
|
||||
// Our CMake configuration can avoid this error on header-only libraries,
|
||||
// but since this library is conditionally empty, including a single
|
||||
// variable is an easy workaround.
|
||||
#ifdef __APPLE__
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
extern const char kAvoidEmptyStringViewLibraryWarning;
|
||||
const char kAvoidEmptyStringViewLibraryWarning = 0;
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
#endif // __APPLE__
|
||||
|
||||
#endif // ABSL_USES_STD_STRING_VIEW
|
||||
@@ -1,4 +1,3 @@
|
||||
//
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -17,784 +16,24 @@
|
||||
// File: string_view.h
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// This file contains the definition of the `absl::string_view` class. A
|
||||
// `string_view` points to a contiguous span of characters, often part or all of
|
||||
// another `std::string`, double-quoted string literal, character array, or even
|
||||
// another `string_view`.
|
||||
//
|
||||
// This `absl::string_view` abstraction is designed to be a drop-in
|
||||
// replacement for the C++17 `std::string_view` abstraction.
|
||||
// Historical note: Abseil once provided an implementation of
|
||||
// `absl::string_view` as a polyfill for `std::string_view` prior to C++17. Now
|
||||
// that C++17 is required, `absl::string_view` is an alias for
|
||||
// `std::string_view`
|
||||
|
||||
#ifndef ABSL_STRINGS_STRING_VIEW_H_
|
||||
#define ABSL_STRINGS_STRING_VIEW_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <iosfwd>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <string_view>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/internal/throw_delegate.h"
|
||||
#include "absl/base/macros.h"
|
||||
#include "absl/base/nullability.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/base/port.h"
|
||||
|
||||
#ifdef ABSL_USES_STD_STRING_VIEW
|
||||
|
||||
#include <string_view> // IWYU pragma: export
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
using string_view = std::string_view;
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#else // ABSL_USES_STD_STRING_VIEW
|
||||
|
||||
#if ABSL_HAVE_ATTRIBUTE(diagnose_if)
|
||||
#define ABSL_INTERNAL_DIAGNOSE_IF_NULLPTR(x) \
|
||||
__attribute__((diagnose_if( \
|
||||
x == nullptr, \
|
||||
"null passed to a callee that requires a non-null argument", "error")))
|
||||
#else
|
||||
#define ABSL_INTERNAL_DIAGNOSE_IF_NULLPTR(x)
|
||||
#endif
|
||||
|
||||
#if ABSL_HAVE_BUILTIN(__builtin_memcmp) || \
|
||||
(defined(__GNUC__) && !defined(__clang__)) || \
|
||||
(defined(_MSC_VER) && _MSC_VER >= 1928)
|
||||
#define ABSL_INTERNAL_STRING_VIEW_MEMCMP __builtin_memcmp
|
||||
#else // ABSL_HAVE_BUILTIN(__builtin_memcmp)
|
||||
#define ABSL_INTERNAL_STRING_VIEW_MEMCMP memcmp
|
||||
#endif // ABSL_HAVE_BUILTIN(__builtin_memcmp)
|
||||
|
||||
// If `std::ranges` is available, mark `string_view` as satisfying the
|
||||
// `view` and `borrowed_range` concepts, just like `std::string_view`.
|
||||
#ifdef __has_include
|
||||
#if __has_include(<version>)
|
||||
#include <version>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
|
||||
#include <ranges> // NOLINT(build/c++20)
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
class string_view;
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
template <>
|
||||
// NOLINTNEXTLINE(build/c++20)
|
||||
inline constexpr bool std::ranges::enable_view<absl::string_view> = true;
|
||||
template <>
|
||||
// NOLINTNEXTLINE(build/c++20)
|
||||
inline constexpr bool std::ranges::enable_borrowed_range<absl::string_view> =
|
||||
true;
|
||||
#endif
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
|
||||
// absl::string_view
|
||||
//
|
||||
// A `string_view` provides a lightweight view into the string data provided by
|
||||
// a `std::string`, double-quoted string literal, character array, or even
|
||||
// another `string_view`. A `string_view` does *not* own the string to which it
|
||||
// points, and that data cannot be modified through the view.
|
||||
//
|
||||
// You can use `string_view` as a function or method parameter anywhere a
|
||||
// parameter can receive a double-quoted string literal, `const char*`,
|
||||
// `std::string`, or another `absl::string_view` argument with no need to copy
|
||||
// the string data. Systematic use of `string_view` within function arguments
|
||||
// reduces data copies and `strlen()` calls.
|
||||
//
|
||||
// Because of its small size, prefer passing `string_view` by value:
|
||||
//
|
||||
// void MyFunction(absl::string_view arg);
|
||||
//
|
||||
// If circumstances require, you may also pass one by const reference:
|
||||
//
|
||||
// void MyFunction(const absl::string_view& arg); // not preferred
|
||||
//
|
||||
// Passing by value generates slightly smaller code for many architectures.
|
||||
//
|
||||
// In either case, the source data of the `string_view` must outlive the
|
||||
// `string_view` itself.
|
||||
//
|
||||
// A `string_view` is also suitable for local variables if you know that the
|
||||
// lifetime of the underlying object is longer than the lifetime of your
|
||||
// `string_view` variable. However, beware of binding a `string_view` to a
|
||||
// temporary value:
|
||||
//
|
||||
// // BAD use of string_view: lifetime problem
|
||||
// absl::string_view sv = obj.ReturnAString();
|
||||
//
|
||||
// // GOOD use of string_view: str outlives sv
|
||||
// std::string str = obj.ReturnAString();
|
||||
// absl::string_view sv = str;
|
||||
//
|
||||
// Due to lifetime issues, a `string_view` is sometimes a poor choice for a
|
||||
// return value and usually a poor choice for a data member. If you do use a
|
||||
// `string_view` this way, it is your responsibility to ensure that the object
|
||||
// pointed to by the `string_view` outlives the `string_view`.
|
||||
//
|
||||
// A `string_view` may represent a whole string or just part of a string. For
|
||||
// example, when splitting a string, `std::vector<absl::string_view>` is a
|
||||
// natural data type for the output.
|
||||
//
|
||||
// For another example, a Cord is a non-contiguous, potentially very
|
||||
// long string-like object. The Cord class has an interface that iteratively
|
||||
// provides string_view objects that point to the successive pieces of a Cord
|
||||
// object.
|
||||
//
|
||||
// When constructed from a source which is NUL-terminated, the `string_view`
|
||||
// itself will not include the NUL-terminator unless a specific size (including
|
||||
// the NUL) is passed to the constructor. As a result, common idioms that work
|
||||
// on NUL-terminated strings do not work on `string_view` objects. If you write
|
||||
// code that scans a `string_view`, you must check its length rather than test
|
||||
// for nul, for example. Note, however, that nuls may still be embedded within
|
||||
// a `string_view` explicitly.
|
||||
//
|
||||
// You may create a null `string_view` in two ways:
|
||||
//
|
||||
// absl::string_view sv;
|
||||
// absl::string_view sv(nullptr, 0);
|
||||
//
|
||||
// For the above, `sv.data() == nullptr`, `sv.length() == 0`, and
|
||||
// `sv.empty() == true`. Also, if you create a `string_view` with a non-null
|
||||
// pointer then `sv.data() != nullptr`. Thus, you can use `string_view()` to
|
||||
// signal an undefined value that is different from other `string_view` values
|
||||
// in a similar fashion to how `const char* p1 = nullptr;` is different from
|
||||
// `const char* p2 = "";`. However, in practice, it is not recommended to rely
|
||||
// on this behavior.
|
||||
//
|
||||
// Be careful not to confuse a null `string_view` with an empty one. A null
|
||||
// `string_view` is an empty `string_view`, but some empty `string_view`s are
|
||||
// not null. Prefer checking for emptiness over checking for null.
|
||||
//
|
||||
// There are many ways to create an empty string_view:
|
||||
//
|
||||
// const char* nullcp = nullptr;
|
||||
// // string_view.size() will return 0 in all cases.
|
||||
// absl::string_view();
|
||||
// absl::string_view(nullcp, 0);
|
||||
// absl::string_view("");
|
||||
// absl::string_view("", 0);
|
||||
// absl::string_view("abcdef", 0);
|
||||
// absl::string_view("abcdef" + 6, 0);
|
||||
//
|
||||
// All empty `string_view` objects whether null or not, are equal:
|
||||
//
|
||||
// absl::string_view() == absl::string_view("", 0)
|
||||
// absl::string_view(nullptr, 0) == absl::string_view("abcdef"+6, 0)
|
||||
class ABSL_ATTRIBUTE_VIEW string_view {
|
||||
public:
|
||||
using traits_type = std::char_traits<char>;
|
||||
using value_type = char;
|
||||
using pointer = char* absl_nullable;
|
||||
using const_pointer = const char* absl_nullable;
|
||||
using reference = char&;
|
||||
using const_reference = const char&;
|
||||
using const_iterator = const char* absl_nullable;
|
||||
using iterator = const_iterator;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
using reverse_iterator = const_reverse_iterator;
|
||||
using size_type = size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using absl_internal_is_view = std::true_type;
|
||||
|
||||
static constexpr size_type npos = static_cast<size_type>(-1);
|
||||
|
||||
// Null `string_view` constructor
|
||||
constexpr string_view() noexcept : ptr_(nullptr), length_(0) {}
|
||||
|
||||
// Implicit constructors
|
||||
|
||||
template <typename Allocator>
|
||||
string_view( // NOLINT(runtime/explicit)
|
||||
const std::basic_string<char, std::char_traits<char>, Allocator>& str
|
||||
ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept
|
||||
// This is implemented in terms of `string_view(p, n)` so `str.size()`
|
||||
// doesn't need to be reevaluated after `ptr_` is set.
|
||||
// The length check is also skipped since it is unnecessary and causes
|
||||
// code bloat.
|
||||
: string_view(str.data(), str.size(), SkipCheckLengthTag{}) {}
|
||||
|
||||
// Implicit constructor of a `string_view` from NUL-terminated `str`. When
|
||||
// accepting possibly null strings, use `absl::NullSafeStringView(str)`
|
||||
// instead (see below).
|
||||
// The length check is skipped since it is unnecessary and causes code bloat.
|
||||
constexpr string_view( // NOLINT(runtime/explicit)
|
||||
const char* absl_nonnull str) ABSL_INTERNAL_DIAGNOSE_IF_NULLPTR(str)
|
||||
: ptr_(str), length_(str ? StrlenInternal(str) : 0) {
|
||||
ABSL_HARDENING_ASSERT(str != nullptr);
|
||||
}
|
||||
|
||||
// Constructor of a `string_view` from a `const char*` and length.
|
||||
constexpr string_view(const char* absl_nullable data, size_type len)
|
||||
: ptr_(data), length_(CheckLengthInternal(len)) {
|
||||
ABSL_ASSERT(data != nullptr || len == 0);
|
||||
}
|
||||
|
||||
#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
|
||||
template <std::contiguous_iterator It, std::sized_sentinel_for<It> End>
|
||||
requires(std::is_same_v<std::iter_value_t<It>, value_type> &&
|
||||
!std::is_convertible_v<End, size_type>)
|
||||
constexpr string_view(It begin, End end)
|
||||
: ptr_(std::to_address(begin)), length_(end - begin) {
|
||||
ABSL_HARDENING_ASSERT(end >= begin);
|
||||
}
|
||||
#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
|
||||
|
||||
// Deleted constructor from std::nullptr_t from C++23.
|
||||
string_view(std::nullptr_t) = delete;
|
||||
|
||||
constexpr string_view(const string_view&) noexcept = default;
|
||||
string_view& operator=(const string_view&) noexcept = default;
|
||||
|
||||
// Iterators
|
||||
|
||||
// string_view::begin()
|
||||
//
|
||||
// Returns an iterator pointing to the first character at the beginning of the
|
||||
// `string_view`, or `end()` if the `string_view` is empty.
|
||||
constexpr const_iterator begin() const noexcept { return ptr_; }
|
||||
|
||||
// string_view::end()
|
||||
//
|
||||
// Returns an iterator pointing just beyond the last character at the end of
|
||||
// the `string_view`. This iterator acts as a placeholder; attempting to
|
||||
// access it results in undefined behavior.
|
||||
constexpr const_iterator end() const noexcept { return ptr_ + length_; }
|
||||
|
||||
// string_view::cbegin()
|
||||
//
|
||||
// Returns a const iterator pointing to the first character at the beginning
|
||||
// of the `string_view`, or `end()` if the `string_view` is empty.
|
||||
constexpr const_iterator cbegin() const noexcept { return begin(); }
|
||||
|
||||
// string_view::cend()
|
||||
//
|
||||
// Returns a const iterator pointing just beyond the last character at the end
|
||||
// of the `string_view`. This pointer acts as a placeholder; attempting to
|
||||
// access its element results in undefined behavior.
|
||||
constexpr const_iterator cend() const noexcept { return end(); }
|
||||
|
||||
// string_view::rbegin()
|
||||
//
|
||||
// Returns a reverse iterator pointing to the last character at the end of the
|
||||
// `string_view`, or `rend()` if the `string_view` is empty.
|
||||
const_reverse_iterator rbegin() const noexcept {
|
||||
return const_reverse_iterator(end());
|
||||
}
|
||||
|
||||
// string_view::rend()
|
||||
//
|
||||
// Returns a reverse iterator pointing just before the first character at the
|
||||
// beginning of the `string_view`. This pointer acts as a placeholder;
|
||||
// attempting to access its element results in undefined behavior.
|
||||
const_reverse_iterator rend() const noexcept {
|
||||
return const_reverse_iterator(begin());
|
||||
}
|
||||
|
||||
// string_view::crbegin()
|
||||
//
|
||||
// Returns a const reverse iterator pointing to the last character at the end
|
||||
// of the `string_view`, or `crend()` if the `string_view` is empty.
|
||||
const_reverse_iterator crbegin() const noexcept { return rbegin(); }
|
||||
|
||||
// string_view::crend()
|
||||
//
|
||||
// Returns a const reverse iterator pointing just before the first character
|
||||
// at the beginning of the `string_view`. This pointer acts as a placeholder;
|
||||
// attempting to access its element results in undefined behavior.
|
||||
const_reverse_iterator crend() const noexcept { return rend(); }
|
||||
|
||||
// Capacity Utilities
|
||||
|
||||
// string_view::size()
|
||||
//
|
||||
// Returns the number of characters in the `string_view`.
|
||||
constexpr size_type size() const noexcept { return length_; }
|
||||
|
||||
// string_view::length()
|
||||
//
|
||||
// Returns the number of characters in the `string_view`. Alias for `size()`.
|
||||
constexpr size_type length() const noexcept { return size(); }
|
||||
|
||||
// string_view::max_size()
|
||||
//
|
||||
// Returns the maximum number of characters the `string_view` can hold.
|
||||
constexpr size_type max_size() const noexcept { return kMaxSize; }
|
||||
|
||||
// string_view::empty()
|
||||
//
|
||||
// Checks if the `string_view` is empty (refers to no characters).
|
||||
constexpr bool empty() const noexcept { return length_ == 0; }
|
||||
|
||||
// string_view::operator[]
|
||||
//
|
||||
// Returns the ith element of the `string_view` using the array operator.
|
||||
// Note that this operator does not perform any bounds checking.
|
||||
constexpr const_reference operator[](size_type i) const {
|
||||
ABSL_HARDENING_ASSERT(i < size());
|
||||
return ptr_[i];
|
||||
}
|
||||
|
||||
// string_view::at()
|
||||
//
|
||||
// Returns the ith element of the `string_view`. Bounds checking is performed,
|
||||
// and an exception of type `std::out_of_range` will be thrown on invalid
|
||||
// access.
|
||||
constexpr const_reference at(size_type i) const {
|
||||
if (ABSL_PREDICT_FALSE(i >= size())) {
|
||||
base_internal::ThrowStdOutOfRange("absl::string_view::at");
|
||||
}
|
||||
return ptr_[i];
|
||||
}
|
||||
|
||||
// string_view::front()
|
||||
//
|
||||
// Returns the first element of a `string_view`.
|
||||
constexpr const_reference front() const {
|
||||
ABSL_HARDENING_ASSERT(!empty());
|
||||
return ptr_[0];
|
||||
}
|
||||
|
||||
// string_view::back()
|
||||
//
|
||||
// Returns the last element of a `string_view`.
|
||||
constexpr const_reference back() const {
|
||||
ABSL_HARDENING_ASSERT(!empty());
|
||||
return ptr_[size() - 1];
|
||||
}
|
||||
|
||||
// string_view::data()
|
||||
//
|
||||
// Returns a pointer to the underlying character array (which is of course
|
||||
// stored elsewhere). Note that `string_view::data()` may contain embedded nul
|
||||
// characters, but the returned buffer may or may not be NUL-terminated;
|
||||
// therefore, do not pass `data()` to a routine that expects a NUL-terminated
|
||||
// string.
|
||||
constexpr const_pointer data() const noexcept { return ptr_; }
|
||||
|
||||
// Modifiers
|
||||
|
||||
// string_view::remove_prefix()
|
||||
//
|
||||
// Removes the first `n` characters from the `string_view`. Note that the
|
||||
// underlying string is not changed, only the view.
|
||||
constexpr void remove_prefix(size_type n) {
|
||||
ABSL_HARDENING_ASSERT(n <= length_);
|
||||
ptr_ += n;
|
||||
length_ -= n;
|
||||
}
|
||||
|
||||
// string_view::remove_suffix()
|
||||
//
|
||||
// Removes the last `n` characters from the `string_view`. Note that the
|
||||
// underlying string is not changed, only the view.
|
||||
constexpr void remove_suffix(size_type n) {
|
||||
ABSL_HARDENING_ASSERT(n <= length_);
|
||||
length_ -= n;
|
||||
}
|
||||
|
||||
// string_view::swap()
|
||||
//
|
||||
// Swaps this `string_view` with another `string_view`.
|
||||
constexpr void swap(string_view& s) noexcept {
|
||||
auto t = *this;
|
||||
*this = s;
|
||||
s = t;
|
||||
}
|
||||
|
||||
// Explicit conversion operators
|
||||
|
||||
// Converts to `std::basic_string`.
|
||||
template <typename A>
|
||||
explicit operator std::basic_string<char, traits_type, A>() const {
|
||||
if (!data()) return {};
|
||||
return std::basic_string<char, traits_type, A>(data(), size());
|
||||
}
|
||||
|
||||
// string_view::copy()
|
||||
//
|
||||
// Copies the contents of the `string_view` at offset `pos` and length `n`
|
||||
// into `buf`.
|
||||
size_type copy(char* absl_nonnull buf, size_type n, size_type pos = 0) const {
|
||||
if (ABSL_PREDICT_FALSE(pos > length_)) {
|
||||
base_internal::ThrowStdOutOfRange("absl::string_view::copy");
|
||||
}
|
||||
size_type rlen = (std::min)(length_ - pos, n);
|
||||
if (rlen > 0) {
|
||||
const char* start = ptr_ + pos;
|
||||
traits_type::copy(buf, start, rlen);
|
||||
}
|
||||
return rlen;
|
||||
}
|
||||
|
||||
// string_view::substr()
|
||||
//
|
||||
// Returns a "substring" of the `string_view` (at offset `pos` and length
|
||||
// `n`) as another string_view. This function throws `std::out_of_bounds` if
|
||||
// `pos > size`.
|
||||
// Use absl::ClippedSubstr if you need a truncating substr operation.
|
||||
constexpr string_view substr(size_type pos = 0, size_type n = npos) const {
|
||||
if (ABSL_PREDICT_FALSE(pos > length_)) {
|
||||
base_internal::ThrowStdOutOfRange("absl::string_view::substr");
|
||||
}
|
||||
return string_view(ptr_ + pos, (std::min)(n, length_ - pos));
|
||||
}
|
||||
|
||||
// string_view::compare()
|
||||
//
|
||||
// Performs a lexicographical comparison between this `string_view` and
|
||||
// another `string_view` `x`, returning a negative value if `*this` is less
|
||||
// than `x`, 0 if `*this` is equal to `x`, and a positive value if `*this`
|
||||
// is greater than `x`.
|
||||
constexpr int compare(string_view x) const noexcept {
|
||||
return CompareImpl(length_, x.length_,
|
||||
(std::min)(length_, x.length_) == 0
|
||||
? 0
|
||||
: ABSL_INTERNAL_STRING_VIEW_MEMCMP(
|
||||
ptr_, x.ptr_, (std::min)(length_, x.length_)));
|
||||
}
|
||||
|
||||
// Overload of `string_view::compare()` for comparing a substring of the
|
||||
// 'string_view` and another `absl::string_view`.
|
||||
constexpr int compare(size_type pos1, size_type count1, string_view v) const {
|
||||
return substr(pos1, count1).compare(v);
|
||||
}
|
||||
|
||||
// Overload of `string_view::compare()` for comparing a substring of the
|
||||
// `string_view` and a substring of another `absl::string_view`.
|
||||
constexpr int compare(size_type pos1, size_type count1, string_view v,
|
||||
size_type pos2, size_type count2) const {
|
||||
return substr(pos1, count1).compare(v.substr(pos2, count2));
|
||||
}
|
||||
|
||||
// Overload of `string_view::compare()` for comparing a `string_view` and a
|
||||
// a different C-style string `s`.
|
||||
constexpr int compare(const char* absl_nonnull s) const {
|
||||
return compare(string_view(s));
|
||||
}
|
||||
|
||||
// Overload of `string_view::compare()` for comparing a substring of the
|
||||
// `string_view` and a different string C-style string `s`.
|
||||
constexpr int compare(size_type pos1, size_type count1,
|
||||
const char* absl_nonnull s) const {
|
||||
return substr(pos1, count1).compare(string_view(s));
|
||||
}
|
||||
|
||||
// Overload of `string_view::compare()` for comparing a substring of the
|
||||
// `string_view` and a substring of a different C-style string `s`.
|
||||
constexpr int compare(size_type pos1, size_type count1,
|
||||
const char* absl_nonnull s, size_type count2) const {
|
||||
return substr(pos1, count1).compare(string_view(s, count2));
|
||||
}
|
||||
|
||||
// Find Utilities
|
||||
|
||||
// string_view::find()
|
||||
//
|
||||
// Finds the first occurrence of the substring `s` within the `string_view`,
|
||||
// returning the position of the first character's match, or `npos` if no
|
||||
// match was found.
|
||||
size_type find(string_view s, size_type pos = 0) const noexcept;
|
||||
|
||||
// Overload of `string_view::find()` for finding the given character `c`
|
||||
// within the `string_view`.
|
||||
size_type find(char c, size_type pos = 0) const noexcept;
|
||||
|
||||
// Overload of `string_view::find()` for finding a substring of a different
|
||||
// C-style string `s` within the `string_view`.
|
||||
size_type find(const char* absl_nonnull s, size_type pos,
|
||||
size_type count) const {
|
||||
return find(string_view(s, count), pos);
|
||||
}
|
||||
|
||||
// Overload of `string_view::find()` for finding a different C-style string
|
||||
// `s` within the `string_view`.
|
||||
size_type find(const char* absl_nonnull s, size_type pos = 0) const {
|
||||
return find(string_view(s), pos);
|
||||
}
|
||||
|
||||
// string_view::rfind()
|
||||
//
|
||||
// Finds the last occurrence of a substring `s` within the `string_view`,
|
||||
// returning the position of the first character's match, or `npos` if no
|
||||
// match was found.
|
||||
size_type rfind(string_view s, size_type pos = npos) const noexcept;
|
||||
|
||||
// Overload of `string_view::rfind()` for finding the last given character `c`
|
||||
// within the `string_view`.
|
||||
size_type rfind(char c, size_type pos = npos) const noexcept;
|
||||
|
||||
// Overload of `string_view::rfind()` for finding a substring of a different
|
||||
// C-style string `s` within the `string_view`.
|
||||
size_type rfind(const char* absl_nonnull s, size_type pos,
|
||||
size_type count) const {
|
||||
return rfind(string_view(s, count), pos);
|
||||
}
|
||||
|
||||
// Overload of `string_view::rfind()` for finding a different C-style string
|
||||
// `s` within the `string_view`.
|
||||
size_type rfind(const char* absl_nonnull s, size_type pos = npos) const {
|
||||
return rfind(string_view(s), pos);
|
||||
}
|
||||
|
||||
// string_view::find_first_of()
|
||||
//
|
||||
// Finds the first occurrence of any of the characters in `s` within the
|
||||
// `string_view`, returning the start position of the match, or `npos` if no
|
||||
// match was found.
|
||||
size_type find_first_of(string_view s, size_type pos = 0) const noexcept;
|
||||
|
||||
// Overload of `string_view::find_first_of()` for finding a character `c`
|
||||
// within the `string_view`.
|
||||
size_type find_first_of(char c, size_type pos = 0) const noexcept {
|
||||
return find(c, pos);
|
||||
}
|
||||
|
||||
// Overload of `string_view::find_first_of()` for finding a substring of a
|
||||
// different C-style string `s` within the `string_view`.
|
||||
size_type find_first_of(const char* absl_nonnull s, size_type pos,
|
||||
size_type count) const {
|
||||
return find_first_of(string_view(s, count), pos);
|
||||
}
|
||||
|
||||
// Overload of `string_view::find_first_of()` for finding a different C-style
|
||||
// string `s` within the `string_view`.
|
||||
size_type find_first_of(const char* absl_nonnull s, size_type pos = 0) const {
|
||||
return find_first_of(string_view(s), pos);
|
||||
}
|
||||
|
||||
// string_view::find_last_of()
|
||||
//
|
||||
// Finds the last occurrence of any of the characters in `s` within the
|
||||
// `string_view`, returning the start position of the match, or `npos` if no
|
||||
// match was found.
|
||||
size_type find_last_of(string_view s, size_type pos = npos) const noexcept;
|
||||
|
||||
// Overload of `string_view::find_last_of()` for finding a character `c`
|
||||
// within the `string_view`.
|
||||
size_type find_last_of(char c, size_type pos = npos) const noexcept {
|
||||
return rfind(c, pos);
|
||||
}
|
||||
|
||||
// Overload of `string_view::find_last_of()` for finding a substring of a
|
||||
// different C-style string `s` within the `string_view`.
|
||||
size_type find_last_of(const char* absl_nonnull s, size_type pos,
|
||||
size_type count) const {
|
||||
return find_last_of(string_view(s, count), pos);
|
||||
}
|
||||
|
||||
// Overload of `string_view::find_last_of()` for finding a different C-style
|
||||
// string `s` within the `string_view`.
|
||||
size_type find_last_of(const char* absl_nonnull s,
|
||||
size_type pos = npos) const {
|
||||
return find_last_of(string_view(s), pos);
|
||||
}
|
||||
|
||||
// string_view::find_first_not_of()
|
||||
//
|
||||
// Finds the first occurrence of any of the characters not in `s` within the
|
||||
// `string_view`, returning the start position of the first non-match, or
|
||||
// `npos` if no non-match was found.
|
||||
size_type find_first_not_of(string_view s, size_type pos = 0) const noexcept;
|
||||
|
||||
// Overload of `string_view::find_first_not_of()` for finding a character
|
||||
// that is not `c` within the `string_view`.
|
||||
size_type find_first_not_of(char c, size_type pos = 0) const noexcept;
|
||||
|
||||
// Overload of `string_view::find_first_not_of()` for finding a substring of a
|
||||
// different C-style string `s` within the `string_view`.
|
||||
size_type find_first_not_of(const char* absl_nonnull s, size_type pos,
|
||||
size_type count) const {
|
||||
return find_first_not_of(string_view(s, count), pos);
|
||||
}
|
||||
|
||||
// Overload of `string_view::find_first_not_of()` for finding a different
|
||||
// C-style string `s` within the `string_view`.
|
||||
size_type find_first_not_of(const char* absl_nonnull s,
|
||||
size_type pos = 0) const {
|
||||
return find_first_not_of(string_view(s), pos);
|
||||
}
|
||||
|
||||
// string_view::find_last_not_of()
|
||||
//
|
||||
// Finds the last occurrence of any of the characters not in `s` within the
|
||||
// `string_view`, returning the start position of the last non-match, or
|
||||
// `npos` if no non-match was found.
|
||||
size_type find_last_not_of(string_view s,
|
||||
size_type pos = npos) const noexcept;
|
||||
|
||||
// Overload of `string_view::find_last_not_of()` for finding a character
|
||||
// that is not `c` within the `string_view`.
|
||||
size_type find_last_not_of(char c, size_type pos = npos) const noexcept;
|
||||
|
||||
// Overload of `string_view::find_last_not_of()` for finding a substring of a
|
||||
// different C-style string `s` within the `string_view`.
|
||||
size_type find_last_not_of(const char* absl_nonnull s, size_type pos,
|
||||
size_type count) const {
|
||||
return find_last_not_of(string_view(s, count), pos);
|
||||
}
|
||||
|
||||
// Overload of `string_view::find_last_not_of()` for finding a different
|
||||
// C-style string `s` within the `string_view`.
|
||||
size_type find_last_not_of(const char* absl_nonnull s,
|
||||
size_type pos = npos) const {
|
||||
return find_last_not_of(string_view(s), pos);
|
||||
}
|
||||
|
||||
#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
|
||||
// string_view::starts_with()
|
||||
//
|
||||
// Returns true if the `string_view` starts with the prefix `s`.
|
||||
//
|
||||
// This method only exists when targeting at least C++20.
|
||||
// If support for C++ prior to C++20 is required, use `absl::StartsWith()`
|
||||
// from `//absl/strings/match.h` for compatibility.
|
||||
constexpr bool starts_with(string_view s) const noexcept {
|
||||
return s.empty() ||
|
||||
(size() >= s.size() &&
|
||||
ABSL_INTERNAL_STRING_VIEW_MEMCMP(data(), s.data(), s.size()) == 0);
|
||||
}
|
||||
|
||||
// Overload of `string_view::starts_with()` that returns true if `c` is the
|
||||
// first character of the `string_view`.
|
||||
constexpr bool starts_with(char c) const noexcept {
|
||||
return !empty() && front() == c;
|
||||
}
|
||||
|
||||
// Overload of `string_view::starts_with()` that returns true if the
|
||||
// `string_view` starts with the C-style prefix `s`.
|
||||
constexpr bool starts_with(const char* absl_nonnull s) const {
|
||||
return starts_with(string_view(s));
|
||||
}
|
||||
|
||||
// string_view::ends_with()
|
||||
//
|
||||
// Returns true if the `string_view` ends with the suffix `s`.
|
||||
//
|
||||
// This method only exists when targeting at least C++20.
|
||||
// If support for C++ prior to C++20 is required, use `absl::EndsWith()`
|
||||
// from `//absl/strings/match.h` for compatibility.
|
||||
constexpr bool ends_with(string_view s) const noexcept {
|
||||
return s.empty() || (size() >= s.size() && ABSL_INTERNAL_STRING_VIEW_MEMCMP(
|
||||
data() + (size() - s.size()),
|
||||
s.data(), s.size()) == 0);
|
||||
}
|
||||
|
||||
// Overload of `string_view::ends_with()` that returns true if `c` is the
|
||||
// last character of the `string_view`.
|
||||
constexpr bool ends_with(char c) const noexcept {
|
||||
return !empty() && back() == c;
|
||||
}
|
||||
|
||||
// Overload of `string_view::ends_with()` that returns true if the
|
||||
// `string_view` ends with the C-style suffix `s`.
|
||||
constexpr bool ends_with(const char* absl_nonnull s) const {
|
||||
return ends_with(string_view(s));
|
||||
}
|
||||
#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
|
||||
|
||||
private:
|
||||
// The constructor from std::string delegates to this constructor.
|
||||
// See the comment on that constructor for the rationale.
|
||||
struct SkipCheckLengthTag {};
|
||||
string_view(const char* absl_nullable data, size_type len,
|
||||
SkipCheckLengthTag) noexcept
|
||||
: ptr_(data), length_(len) {}
|
||||
|
||||
static constexpr size_type kMaxSize =
|
||||
(std::numeric_limits<difference_type>::max)();
|
||||
|
||||
static constexpr size_type CheckLengthInternal(size_type len) {
|
||||
ABSL_HARDENING_ASSERT(len <= kMaxSize);
|
||||
return len;
|
||||
}
|
||||
|
||||
static constexpr size_type StrlenInternal(const char* absl_nonnull str) {
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
// MSVC 2017+ can evaluate this at compile-time.
|
||||
const char* begin = str;
|
||||
while (*str != '\0') ++str;
|
||||
return str - begin;
|
||||
#elif ABSL_HAVE_BUILTIN(__builtin_strlen) || \
|
||||
(defined(__GNUC__) && !defined(__clang__))
|
||||
// GCC has __builtin_strlen according to
|
||||
// https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Other-Builtins.html, but
|
||||
// ABSL_HAVE_BUILTIN doesn't detect that, so we use the extra checks above.
|
||||
// __builtin_strlen is constexpr.
|
||||
return __builtin_strlen(str);
|
||||
#else
|
||||
return str ? strlen(str) : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static constexpr int CompareImpl(size_type length_a, size_type length_b,
|
||||
int compare_result) {
|
||||
return compare_result == 0 ? static_cast<int>(length_a > length_b) -
|
||||
static_cast<int>(length_a < length_b)
|
||||
: (compare_result < 0 ? -1 : 1);
|
||||
}
|
||||
|
||||
const char* absl_nullable ptr_;
|
||||
size_type length_;
|
||||
};
|
||||
|
||||
// This large function is defined inline so that in a fairly common case where
|
||||
// one of the arguments is a literal, the compiler can elide a lot of the
|
||||
// following comparisons.
|
||||
constexpr bool operator==(string_view x, string_view y) noexcept {
|
||||
return x.size() == y.size() &&
|
||||
(x.empty() ||
|
||||
ABSL_INTERNAL_STRING_VIEW_MEMCMP(x.data(), y.data(), x.size()) == 0);
|
||||
}
|
||||
|
||||
constexpr bool operator!=(string_view x, string_view y) noexcept {
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
constexpr bool operator<(string_view x, string_view y) noexcept {
|
||||
return x.compare(y) < 0;
|
||||
}
|
||||
|
||||
constexpr bool operator>(string_view x, string_view y) noexcept {
|
||||
return y < x;
|
||||
}
|
||||
|
||||
constexpr bool operator<=(string_view x, string_view y) noexcept {
|
||||
return !(y < x);
|
||||
}
|
||||
|
||||
constexpr bool operator>=(string_view x, string_view y) noexcept {
|
||||
return !(x < y);
|
||||
}
|
||||
|
||||
// IO Insertion Operator
|
||||
std::ostream& operator<<(std::ostream& o, string_view piece);
|
||||
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#undef ABSL_INTERNAL_DIAGNOSE_IF_NULLPTR
|
||||
#undef ABSL_INTERNAL_STRING_VIEW_MEMCMP
|
||||
|
||||
#endif // ABSL_USES_STD_STRING_VIEW
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
using std::string_view;
|
||||
|
||||
// ClippedSubstr()
|
||||
//
|
||||
|
||||
@@ -1,380 +0,0 @@
|
||||
// Copyright 2018 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 <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/base/internal/raw_logging.h"
|
||||
#include "absl/base/macros.h"
|
||||
#include "absl/random/random.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void BM_StringViewFromString(benchmark::State& state) {
|
||||
std::string s(state.range(0), 'x');
|
||||
std::string* ps = &s;
|
||||
struct SV {
|
||||
SV() = default;
|
||||
explicit SV(const std::string& s) : sv(s) {}
|
||||
absl::string_view sv;
|
||||
} sv;
|
||||
SV* psv = &sv;
|
||||
benchmark::DoNotOptimize(ps);
|
||||
benchmark::DoNotOptimize(psv);
|
||||
for (auto _ : state) {
|
||||
new (psv) SV(*ps);
|
||||
benchmark::DoNotOptimize(sv);
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_StringViewFromString)->Arg(12)->Arg(128);
|
||||
|
||||
// Provide a forcibly out-of-line wrapper for operator== that can be used in
|
||||
// benchmarks to measure the impact of inlining.
|
||||
ABSL_ATTRIBUTE_NOINLINE
|
||||
bool NonInlinedEq(absl::string_view a, absl::string_view b) { return a == b; }
|
||||
|
||||
// We use functions that cannot be inlined to perform the comparison loops so
|
||||
// that inlining of the operator== can't optimize away *everything*.
|
||||
ABSL_ATTRIBUTE_NOINLINE
|
||||
void DoEqualityComparisons(benchmark::State& state, absl::string_view a,
|
||||
absl::string_view b) {
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(a == b);
|
||||
}
|
||||
}
|
||||
|
||||
void BM_EqualIdentical(benchmark::State& state) {
|
||||
std::string x(state.range(0), 'a');
|
||||
DoEqualityComparisons(state, x, x);
|
||||
}
|
||||
BENCHMARK(BM_EqualIdentical)->DenseRange(0, 3)->Range(4, 1 << 10);
|
||||
|
||||
void BM_EqualSame(benchmark::State& state) {
|
||||
std::string x(state.range(0), 'a');
|
||||
std::string y = x;
|
||||
DoEqualityComparisons(state, x, y);
|
||||
}
|
||||
BENCHMARK(BM_EqualSame)
|
||||
->DenseRange(0, 10)
|
||||
->Arg(20)
|
||||
->Arg(40)
|
||||
->Arg(70)
|
||||
->Arg(110)
|
||||
->Range(160, 4096);
|
||||
|
||||
void BM_EqualDifferent(benchmark::State& state) {
|
||||
const int len = state.range(0);
|
||||
std::string x(len, 'a');
|
||||
std::string y = x;
|
||||
if (len > 0) {
|
||||
y[len - 1] = 'b';
|
||||
}
|
||||
DoEqualityComparisons(state, x, y);
|
||||
}
|
||||
BENCHMARK(BM_EqualDifferent)->DenseRange(0, 3)->Range(4, 1 << 10);
|
||||
|
||||
// This benchmark is intended to check that important simplifications can be
|
||||
// made with absl::string_view comparisons against constant strings. The idea is
|
||||
// that if constant strings cause redundant components of the comparison, the
|
||||
// compiler should detect and eliminate them. Here we use 8 different strings,
|
||||
// each with the same size. Provided our comparison makes the implementation
|
||||
// inline-able by the compiler, it should fold all of these away into a single
|
||||
// size check once per loop iteration.
|
||||
ABSL_ATTRIBUTE_NOINLINE
|
||||
void DoConstantSizeInlinedEqualityComparisons(benchmark::State& state,
|
||||
absl::string_view a) {
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(a == "aaa");
|
||||
benchmark::DoNotOptimize(a == "bbb");
|
||||
benchmark::DoNotOptimize(a == "ccc");
|
||||
benchmark::DoNotOptimize(a == "ddd");
|
||||
benchmark::DoNotOptimize(a == "eee");
|
||||
benchmark::DoNotOptimize(a == "fff");
|
||||
benchmark::DoNotOptimize(a == "ggg");
|
||||
benchmark::DoNotOptimize(a == "hhh");
|
||||
}
|
||||
}
|
||||
void BM_EqualConstantSizeInlined(benchmark::State& state) {
|
||||
std::string x(state.range(0), 'a');
|
||||
DoConstantSizeInlinedEqualityComparisons(state, x);
|
||||
}
|
||||
// We only need to check for size of 3, and <> 3 as this benchmark only has to
|
||||
// do with size differences.
|
||||
BENCHMARK(BM_EqualConstantSizeInlined)->DenseRange(2, 4);
|
||||
|
||||
// This benchmark exists purely to give context to the above timings: this is
|
||||
// what they would look like if the compiler is completely unable to simplify
|
||||
// between two comparisons when they are comparing against constant strings.
|
||||
ABSL_ATTRIBUTE_NOINLINE
|
||||
void DoConstantSizeNonInlinedEqualityComparisons(benchmark::State& state,
|
||||
absl::string_view a) {
|
||||
for (auto _ : state) {
|
||||
// Force these out-of-line to compare with the above function.
|
||||
benchmark::DoNotOptimize(NonInlinedEq(a, "aaa"));
|
||||
benchmark::DoNotOptimize(NonInlinedEq(a, "bbb"));
|
||||
benchmark::DoNotOptimize(NonInlinedEq(a, "ccc"));
|
||||
benchmark::DoNotOptimize(NonInlinedEq(a, "ddd"));
|
||||
benchmark::DoNotOptimize(NonInlinedEq(a, "eee"));
|
||||
benchmark::DoNotOptimize(NonInlinedEq(a, "fff"));
|
||||
benchmark::DoNotOptimize(NonInlinedEq(a, "ggg"));
|
||||
benchmark::DoNotOptimize(NonInlinedEq(a, "hhh"));
|
||||
}
|
||||
}
|
||||
|
||||
void BM_EqualConstantSizeNonInlined(benchmark::State& state) {
|
||||
std::string x(state.range(0), 'a');
|
||||
DoConstantSizeNonInlinedEqualityComparisons(state, x);
|
||||
}
|
||||
// We only need to check for size of 3, and <> 3 as this benchmark only has to
|
||||
// do with size differences.
|
||||
BENCHMARK(BM_EqualConstantSizeNonInlined)->DenseRange(2, 4);
|
||||
|
||||
void BM_CompareSame(benchmark::State& state) {
|
||||
const int len = state.range(0);
|
||||
std::string x;
|
||||
for (int i = 0; i < len; i++) {
|
||||
x += 'a';
|
||||
}
|
||||
std::string y = x;
|
||||
absl::string_view a = x;
|
||||
absl::string_view b = y;
|
||||
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(a);
|
||||
benchmark::DoNotOptimize(b);
|
||||
benchmark::DoNotOptimize(a.compare(b));
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_CompareSame)->DenseRange(0, 3)->Range(4, 1 << 10);
|
||||
|
||||
void BM_CompareFirstOneLess(benchmark::State& state) {
|
||||
const int len = state.range(0);
|
||||
std::string x(len, 'a');
|
||||
std::string y = x;
|
||||
y.back() = 'b';
|
||||
absl::string_view a = x;
|
||||
absl::string_view b = y;
|
||||
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(a);
|
||||
benchmark::DoNotOptimize(b);
|
||||
benchmark::DoNotOptimize(a.compare(b));
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_CompareFirstOneLess)->DenseRange(1, 3)->Range(4, 1 << 10);
|
||||
|
||||
void BM_CompareSecondOneLess(benchmark::State& state) {
|
||||
const int len = state.range(0);
|
||||
std::string x(len, 'a');
|
||||
std::string y = x;
|
||||
x.back() = 'b';
|
||||
absl::string_view a = x;
|
||||
absl::string_view b = y;
|
||||
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(a);
|
||||
benchmark::DoNotOptimize(b);
|
||||
benchmark::DoNotOptimize(a.compare(b));
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_CompareSecondOneLess)->DenseRange(1, 3)->Range(4, 1 << 10);
|
||||
|
||||
void BM_find_string_view_len_one(benchmark::State& state) {
|
||||
std::string haystack(state.range(0), '0');
|
||||
absl::string_view s(haystack);
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(s.find("x")); // not present; length 1
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_find_string_view_len_one)->Range(1, 1 << 20);
|
||||
|
||||
void BM_find_string_view_len_two(benchmark::State& state) {
|
||||
std::string haystack(state.range(0), '0');
|
||||
absl::string_view s(haystack);
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(s.find("xx")); // not present; length 2
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_find_string_view_len_two)->Range(1, 1 << 20);
|
||||
|
||||
void BM_find_one_char(benchmark::State& state) {
|
||||
std::string haystack(state.range(0), '0');
|
||||
absl::string_view s(haystack);
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(s.find('x')); // not present
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_find_one_char)->Range(1, 1 << 20);
|
||||
|
||||
void BM_rfind_one_char(benchmark::State& state) {
|
||||
std::string haystack(state.range(0), '0');
|
||||
absl::string_view s(haystack);
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(s.rfind('x')); // not present
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_rfind_one_char)->Range(1, 1 << 20);
|
||||
|
||||
void BM_worst_case_find_first_of(benchmark::State& state, int haystack_len) {
|
||||
const int needle_len = state.range(0);
|
||||
std::string needle;
|
||||
for (int i = 0; i < needle_len; ++i) {
|
||||
needle += 'a' + i;
|
||||
}
|
||||
std::string haystack(haystack_len, '0'); // 1000 zeros.
|
||||
|
||||
absl::string_view s(haystack);
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(s.find_first_of(needle));
|
||||
}
|
||||
}
|
||||
|
||||
void BM_find_first_of_short(benchmark::State& state) {
|
||||
BM_worst_case_find_first_of(state, 10);
|
||||
}
|
||||
|
||||
void BM_find_first_of_medium(benchmark::State& state) {
|
||||
BM_worst_case_find_first_of(state, 100);
|
||||
}
|
||||
|
||||
void BM_find_first_of_long(benchmark::State& state) {
|
||||
BM_worst_case_find_first_of(state, 1000);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_find_first_of_short)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32);
|
||||
BENCHMARK(BM_find_first_of_medium)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32);
|
||||
BENCHMARK(BM_find_first_of_long)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32);
|
||||
|
||||
struct EasyMap : public std::map<absl::string_view, uint64_t> {
|
||||
explicit EasyMap(size_t) {}
|
||||
};
|
||||
|
||||
// This templated benchmark helper function is intended to stress operator== or
|
||||
// operator< in a realistic test. It surely isn't entirely realistic, but it's
|
||||
// a start. The test creates a map of type Map, a template arg, and populates
|
||||
// it with table_size key/value pairs. Each key has WordsPerKey words. After
|
||||
// creating the map, a number of lookups are done in random order. Some keys
|
||||
// are used much more frequently than others in this phase of the test.
|
||||
template <typename Map, int WordsPerKey>
|
||||
void StringViewMapBenchmark(benchmark::State& state) {
|
||||
const int table_size = state.range(0);
|
||||
const double kFractionOfKeysThatAreHot = 0.2;
|
||||
const int kNumLookupsOfHotKeys = 20;
|
||||
const int kNumLookupsOfColdKeys = 1;
|
||||
const char* words[] = {"the", "quick", "brown", "fox", "jumped",
|
||||
"over", "the", "lazy", "dog", "and",
|
||||
"found", "a", "large", "mushroom", "and",
|
||||
"a", "couple", "crickets", "eating", "pie"};
|
||||
// Create some keys that consist of words in random order.
|
||||
absl::InsecureBitGen rng;
|
||||
std::vector<std::string> keys(table_size);
|
||||
std::vector<int> all_indices;
|
||||
const int kBlockSize = 1 << 12;
|
||||
std::unordered_set<std::string> t(kBlockSize);
|
||||
std::uniform_int_distribution<int> uniform(0, ABSL_ARRAYSIZE(words) - 1);
|
||||
for (int i = 0; i < table_size; i++) {
|
||||
all_indices.push_back(i);
|
||||
do {
|
||||
keys[i].clear();
|
||||
for (int j = 0; j < WordsPerKey; j++) {
|
||||
absl::StrAppend(&keys[i], j > 0 ? " " : "", words[uniform(rng)]);
|
||||
}
|
||||
} while (!t.insert(keys[i]).second);
|
||||
}
|
||||
|
||||
// Create a list of strings to lookup: a permutation of the array of
|
||||
// keys we just created, with repeats. "Hot" keys get repeated more.
|
||||
std::shuffle(all_indices.begin(), all_indices.end(), rng);
|
||||
const int num_hot = table_size * kFractionOfKeysThatAreHot;
|
||||
const int num_cold = table_size - num_hot;
|
||||
std::vector<int> hot_indices(all_indices.begin(),
|
||||
all_indices.begin() + num_hot);
|
||||
std::vector<int> indices;
|
||||
for (int i = 0; i < kNumLookupsOfColdKeys; i++) {
|
||||
indices.insert(indices.end(), all_indices.begin(), all_indices.end());
|
||||
}
|
||||
for (int i = 0; i < kNumLookupsOfHotKeys - kNumLookupsOfColdKeys; i++) {
|
||||
indices.insert(indices.end(), hot_indices.begin(), hot_indices.end());
|
||||
}
|
||||
std::shuffle(indices.begin(), indices.end(), rng);
|
||||
ABSL_RAW_CHECK(
|
||||
num_cold * kNumLookupsOfColdKeys + num_hot * kNumLookupsOfHotKeys ==
|
||||
indices.size(),
|
||||
"");
|
||||
// After constructing the array we probe it with absl::string_views built from
|
||||
// test_strings. This means operator== won't see equal pointers, so
|
||||
// it'll have to check for equal lengths and equal characters.
|
||||
std::vector<std::string> test_strings(indices.size());
|
||||
for (int i = 0; i < indices.size(); i++) {
|
||||
test_strings[i] = keys[indices[i]];
|
||||
}
|
||||
|
||||
// Run the benchmark. It includes map construction but is mostly
|
||||
// map lookups.
|
||||
for (auto _ : state) {
|
||||
Map h(table_size);
|
||||
for (int i = 0; i < table_size; i++) {
|
||||
h[keys[i]] = i * 2;
|
||||
}
|
||||
ABSL_RAW_CHECK(h.size() == table_size, "");
|
||||
uint64_t sum = 0;
|
||||
for (int i = 0; i < indices.size(); i++) {
|
||||
sum += h[test_strings[i]];
|
||||
}
|
||||
benchmark::DoNotOptimize(sum);
|
||||
}
|
||||
}
|
||||
|
||||
void BM_StdMap_4(benchmark::State& state) {
|
||||
StringViewMapBenchmark<EasyMap, 4>(state);
|
||||
}
|
||||
BENCHMARK(BM_StdMap_4)->Range(1 << 10, 1 << 16);
|
||||
|
||||
void BM_StdMap_8(benchmark::State& state) {
|
||||
StringViewMapBenchmark<EasyMap, 8>(state);
|
||||
}
|
||||
BENCHMARK(BM_StdMap_8)->Range(1 << 10, 1 << 16);
|
||||
|
||||
void BM_CopyToStringNative(benchmark::State& state) {
|
||||
std::string src(state.range(0), 'x');
|
||||
absl::string_view sv(src);
|
||||
std::string dst;
|
||||
for (auto _ : state) {
|
||||
dst.assign(sv.begin(), sv.end());
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_CopyToStringNative)->Range(1 << 3, 1 << 12);
|
||||
|
||||
void BM_AppendToStringNative(benchmark::State& state) {
|
||||
std::string src(state.range(0), 'x');
|
||||
absl::string_view sv(src);
|
||||
std::string dst;
|
||||
for (auto _ : state) {
|
||||
dst.clear();
|
||||
dst.insert(dst.end(), sv.begin(), sv.end());
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_AppendToStringNative)->Range(1 << 3, 1 << 12);
|
||||
|
||||
} // namespace
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,6 @@
|
||||
#ifndef ABSL_CI_ABSL_ALTERNATE_OPTIONS_H_
|
||||
#define ABSL_CI_ABSL_ALTERNATE_OPTIONS_H_
|
||||
|
||||
#define ABSL_OPTION_USE_STD_STRING_VIEW 0
|
||||
#define ABSL_OPTION_USE_STD_ORDERING 0
|
||||
#define ABSL_OPTION_USE_INLINE_NAMESPACE 1
|
||||
#define ABSL_OPTION_INLINE_NAMESPACE_NAME ns
|
||||
|
||||
Reference in New Issue
Block a user