diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index b950ec76..e72db82c 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -285,6 +285,7 @@ cc_library( "//absl/container:inlined_vector", "//absl/functional:function_ref", "//absl/meta:type_traits", + "//absl/types:optional", ], ) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index cebc5928..0757a9cb 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -546,6 +546,7 @@ absl_cc_library( absl::fixed_array absl::function_ref absl::inlined_vector + absl::optional absl::raw_logging_internal absl::type_traits PUBLIC diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 68a7e52f..29ed7f75 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -53,6 +53,7 @@ #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/string_view.h" +#include "absl/types/optional.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -512,6 +513,10 @@ class Cord { // REQUIRES: 0 <= i < size() char operator[](size_t i) const; + // If this cord's representation is a single flat array, return a + // string_view referencing that array. Otherwise return nullopt. + absl::optional TryFlat() const; + // Flattens the cord into a single array and returns a view of the data. // // If the cord was already flat, the contents are not modified. @@ -630,7 +635,7 @@ class Cord { // Helper for MemoryUsage() static size_t MemoryUsageAux(const absl::cord_internal::CordRep* rep); - // Helper for GetFlat() + // Helper for GetFlat() and TryFlat() static bool GetFlatAux(absl::cord_internal::CordRep* rep, absl::string_view* fragment); @@ -942,6 +947,18 @@ inline size_t Cord::EstimatedMemoryUsage() const { return result; } +inline absl::optional Cord::TryFlat() const { + absl::cord_internal::CordRep* rep = contents_.tree(); + if (rep == nullptr) { + return absl::string_view(contents_.data(), contents_.size()); + } + absl::string_view fragment; + if (GetFlatAux(rep, &fragment)) { + return fragment; + } + return absl::nullopt; +} + inline absl::string_view Cord::Flatten() { absl::cord_internal::CordRep* rep = contents_.tree(); if (rep == nullptr) { diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 68515cbf..a683cc4b 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -70,9 +70,8 @@ static std::string RandomLowercaseString(RandomEngine* rng) { static std::string RandomLowercaseString(RandomEngine* rng, size_t length) { std::string result(length, '\0'); std::uniform_int_distribution chars('a', 'z'); - std::generate(result.begin(), result.end(), [&]() { - return static_cast(chars(*rng)); - }); + std::generate(result.begin(), result.end(), + [&]() { return static_cast(chars(*rng)); }); return result; } @@ -424,6 +423,50 @@ TEST(Cord, CopyToString) { "copying ", "to ", "a ", "string."})); } +TEST(TryFlat, Empty) { + absl::Cord c; + EXPECT_EQ(c.TryFlat(), ""); +} + +TEST(TryFlat, Flat) { + absl::Cord c("hello"); + EXPECT_EQ(c.TryFlat(), "hello"); +} + +TEST(TryFlat, SubstrInlined) { + absl::Cord c("hello"); + c.RemovePrefix(1); + EXPECT_EQ(c.TryFlat(), "ello"); +} + +TEST(TryFlat, SubstrFlat) { + absl::Cord c("longer than 15 bytes"); + c.RemovePrefix(1); + EXPECT_EQ(c.TryFlat(), "onger than 15 bytes"); +} + +TEST(TryFlat, Concat) { + absl::Cord c = absl::MakeFragmentedCord({"hel", "lo"}); + EXPECT_EQ(c.TryFlat(), absl::nullopt); +} + +TEST(TryFlat, External) { + absl::Cord c = absl::MakeCordFromExternal("hell", [](absl::string_view) {}); + EXPECT_EQ(c.TryFlat(), "hell"); +} + +TEST(TryFlat, SubstrExternal) { + absl::Cord c = absl::MakeCordFromExternal("hell", [](absl::string_view) {}); + c.RemovePrefix(1); + EXPECT_EQ(c.TryFlat(), "ell"); +} + +TEST(TryFlat, SubstrConcat) { + absl::Cord c = absl::MakeFragmentedCord({"hello", " world"}); + c.RemovePrefix(1); + EXPECT_EQ(c.TryFlat(), absl::nullopt); +} + static bool IsFlat(const absl::Cord& c) { return c.chunk_begin() == c.chunk_end() || ++c.chunk_begin() == c.chunk_end(); } @@ -511,24 +554,24 @@ TEST(Cord, MultipleLengths) { for (size_t i = 0; i < d.size(); i++) { std::string a = d.data(i); - { // Construct from Cord + { // Construct from Cord absl::Cord tmp(a); absl::Cord x(tmp); EXPECT_EQ(a, std::string(x)) << "'" << a << "'"; } - { // Construct from absl::string_view + { // Construct from absl::string_view absl::Cord x(a); EXPECT_EQ(a, std::string(x)) << "'" << a << "'"; } - { // Append cord to self + { // Append cord to self absl::Cord self(a); self.Append(self); EXPECT_EQ(a + a, std::string(self)) << "'" << a << "' + '" << a << "'"; } - { // Prepend cord to self + { // Prepend cord to self absl::Cord self(a); self.Prepend(self); EXPECT_EQ(a + a, std::string(self)) << "'" << a << "' + '" << a << "'"; @@ -538,40 +581,40 @@ TEST(Cord, MultipleLengths) { for (size_t j = 0; j < d.size(); j++) { std::string b = d.data(j); - { // CopyFrom Cord + { // CopyFrom Cord absl::Cord x(a); absl::Cord y(b); x = y; EXPECT_EQ(b, std::string(x)) << "'" << a << "' + '" << b << "'"; } - { // CopyFrom absl::string_view + { // CopyFrom absl::string_view absl::Cord x(a); x = b; EXPECT_EQ(b, std::string(x)) << "'" << a << "' + '" << b << "'"; } - { // Cord::Append(Cord) + { // Cord::Append(Cord) absl::Cord x(a); absl::Cord y(b); x.Append(y); EXPECT_EQ(a + b, std::string(x)) << "'" << a << "' + '" << b << "'"; } - { // Cord::Append(absl::string_view) + { // Cord::Append(absl::string_view) absl::Cord x(a); x.Append(b); EXPECT_EQ(a + b, std::string(x)) << "'" << a << "' + '" << b << "'"; } - { // Cord::Prepend(Cord) + { // Cord::Prepend(Cord) absl::Cord x(a); absl::Cord y(b); x.Prepend(y); EXPECT_EQ(b + a, std::string(x)) << "'" << b << "' + '" << a << "'"; } - { // Cord::Prepend(absl::string_view) + { // Cord::Prepend(absl::string_view) absl::Cord x(a); x.Prepend(b); EXPECT_EQ(b + a, std::string(x)) << "'" << b << "' + '" << a << "'"; @@ -1089,7 +1132,7 @@ TEST(ConstructFromExternal, ReferenceQualifierOverloads) { } TEST(ExternalMemory, BasicUsage) { - static const char* strings[] = { "", "hello", "there" }; + static const char* strings[] = {"", "hello", "there"}; for (const char* str : strings) { absl::Cord dst("(prefix)"); AddExternalMemory(str, &dst); diff --git a/absl/types/span.h b/absl/types/span.h index 25347c63..21cda34b 100644 --- a/absl/types/span.h +++ b/absl/types/span.h @@ -292,60 +292,74 @@ class Span { // Span::front() // - // Returns a reference to the first element of this span. + // Returns a reference to the first element of this span. The span must not + // be empty. constexpr reference front() const noexcept { return ABSL_ASSERT(size() > 0), *data(); } // Span::back() // - // Returns a reference to the last element of this span. + // Returns a reference to the last element of this span. The span must not + // be empty. constexpr reference back() const noexcept { return ABSL_ASSERT(size() > 0), *(data() + size() - 1); } // Span::begin() // - // Returns an iterator to the first element of this span. + // Returns an iterator pointing to the first element of this span, or `end()` + // if the span is empty. constexpr iterator begin() const noexcept { return data(); } // Span::cbegin() // - // Returns a const iterator to the first element of this span. + // Returns a const iterator pointing to the first element of this span, or + // `end()` if the span is empty. constexpr const_iterator cbegin() const noexcept { return begin(); } // Span::end() // - // Returns an iterator to the last element of this span. + // Returns an iterator pointing just beyond the last element at the + // end of this span. This iterator acts as a placeholder; attempting to + // access it results in undefined behavior. constexpr iterator end() const noexcept { return data() + size(); } // Span::cend() // - // Returns a const iterator to the last element of this span. + // Returns a const iterator pointing just beyond the last element at the + // end of this span. This iterator acts as a placeholder; attempting to + // access it results in undefined behavior. constexpr const_iterator cend() const noexcept { return end(); } // Span::rbegin() // - // Returns a reverse iterator starting at the last element of this span. + // Returns a reverse iterator pointing to the last element at the end of this + // span, or `rend()` if the span is empty. constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } // Span::crbegin() // - // Returns a reverse const iterator starting at the last element of this span. + // Returns a const reverse iterator pointing to the last element at the end of + // this span, or `crend()` if the span is empty. constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } // Span::rend() // - // Returns a reverse iterator starting at the first element of this span. + // Returns a reverse iterator pointing just before the first element + // at the beginning of this span. This pointer acts as a placeholder; + // attempting to access its element results in undefined behavior. constexpr reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } // Span::crend() // - // Returns a reverse iterator starting at the first element of this span. + // Returns a reverse const iterator pointing just before the first element + // at the beginning of this span. This pointer acts as a placeholder; + // attempting to access its element results in undefined behavior. constexpr const_reverse_iterator crend() const noexcept { return rend(); } // Span mutations