Automated rollback of commit 1db8ed47c29fa04b51df373ce5bbb7e7f95cbd56.
PiperOrigin-RevId: 591045566
diff --git a/src/google/protobuf/generated_message_reflection.cc b/src/google/protobuf/generated_message_reflection.cc
index 0c914e8..647b4cc 100644
--- a/src/google/protobuf/generated_message_reflection.cc
+++ b/src/google/protobuf/generated_message_reflection.cc
@@ -1431,7 +1431,7 @@
switch (field->options().ctype()) {
default: // TODO: Support other string reps.
case FieldOptions::STRING:
- MutableRaw<RepeatedPtrFieldBase>(message, field)->Clear();
+ MutableRaw<RepeatedPtrField<std::string> >(message, field)->Clear();
break;
}
break;
@@ -1441,7 +1441,10 @@
if (IsMapFieldInApi(field)) {
MutableRaw<MapFieldBase>(message, field)->Clear();
} else {
- MutableRaw<RepeatedPtrFieldBase>(message, field)->Clear();
+ // We don't know which subclass of RepeatedPtrFieldBase the type is,
+ // so we use RepeatedPtrFieldBase directly.
+ MutableRaw<RepeatedPtrFieldBase>(message, field)
+ ->Clear<GenericTypeHandler<Message> >();
}
break;
}
@@ -1478,7 +1481,8 @@
switch (field->options().ctype()) {
default: // TODO: Support other string reps.
case FieldOptions::STRING:
- MutableRaw<RepeatedPtrFieldBase>(message, field)->RemoveLast();
+ MutableRaw<RepeatedPtrField<std::string> >(message, field)
+ ->RemoveLast();
break;
}
break;
@@ -1487,9 +1491,10 @@
if (IsMapFieldInApi(field)) {
MutableRaw<MapFieldBase>(message, field)
->MutableRepeatedField()
- ->RemoveLast();
+ ->RemoveLast<GenericTypeHandler<Message> >();
} else {
- MutableRaw<RepeatedPtrFieldBase>(message, field)->RemoveLast();
+ MutableRaw<RepeatedPtrFieldBase>(message, field)
+ ->RemoveLast<GenericTypeHandler<Message> >();
}
break;
}
diff --git a/src/google/protobuf/implicit_weak_message.h b/src/google/protobuf/implicit_weak_message.h
index 7856c22..708f73a 100644
--- a/src/google/protobuf/implicit_weak_message.h
+++ b/src/google/protobuf/implicit_weak_message.h
@@ -201,7 +201,7 @@
}
T* Add() { return weak.Add(); }
- void Clear() { base().Clear(); }
+ void Clear() { base().template Clear<TypeHandler>(); }
void MergeFrom(const WeakRepeatedPtrField& other) {
if (other.empty()) return;
base().template MergeFrom<MessageLite>(other.base());
diff --git a/src/google/protobuf/repeated_field_unittest.cc b/src/google/protobuf/repeated_field_unittest.cc
index e2c3294..d99aae6 100644
--- a/src/google/protobuf/repeated_field_unittest.cc
+++ b/src/google/protobuf/repeated_field_unittest.cc
@@ -1658,6 +1658,7 @@
EXPECT_EQ(field.ClearedCount(), 0);
field.RemoveLast();
+ EXPECT_TRUE(original->empty());
EXPECT_EQ(field.ClearedCount(), 1);
EXPECT_EQ(field.Add(),
diff --git a/src/google/protobuf/repeated_ptr_field.cc b/src/google/protobuf/repeated_ptr_field.cc
index 4d92937..49c29fe 100644
--- a/src/google/protobuf/repeated_ptr_field.cc
+++ b/src/google/protobuf/repeated_ptr_field.cc
@@ -54,7 +54,7 @@
new_rep = reinterpret_cast<Rep*>(Arena::CreateArray<char>(arena, bytes));
}
- if (using_element()) {
+ if (using_sso()) {
new_rep->allocated_size = tagged_rep_or_elem_ != nullptr ? 1 : 0;
new_rep->elements[0] = tagged_rep_or_elem_;
} else {
@@ -75,7 +75,7 @@
tagged_rep_or_elem_ =
reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(new_rep) + 1);
- capacity_proxy_ = new_capacity - kInlinedCapacity;
+ capacity_proxy_ = new_capacity - kSSOCapacity;
return &new_rep->elements[current_size_];
}
@@ -94,31 +94,18 @@
tagged_rep_or_elem_ = nullptr;
}
-namespace {
-template <typename T>
-struct ElementRecycler {
- static void clear(void* p) { static_cast<T*>(p)->Clear(); }
-};
-
-template <>
-struct ElementRecycler<std::string> {
- static void clear(void* str) { static_cast<std::string*>(str)->clear(); }
-};
-
-} // namespace
-
-template <typename Recycler, typename Factory>
-void* RepeatedPtrFieldBase::AddInternal(Factory factory) {
+template <typename F>
+auto* RepeatedPtrFieldBase::AddInternal(F factory) {
Arena* const arena = GetArena();
+ using Result = decltype(factory(arena));
if (tagged_rep_or_elem_ == nullptr) {
ExchangeCurrentSize(1);
tagged_rep_or_elem_ = factory(arena);
- return tagged_rep_or_elem_;
+ return static_cast<Result>(tagged_rep_or_elem_);
}
- if (using_element()) {
+ if (using_sso()) {
if (ExchangeCurrentSize(1) == 0) {
- Recycler::clear(tagged_rep_or_elem_);
- return tagged_rep_or_elem_;
+ return static_cast<Result>(tagged_rep_or_elem_);
}
} else {
absl::PrefetchToLocalCache(rep());
@@ -128,28 +115,23 @@
} else {
Rep* r = rep();
if (current_size_ != r->allocated_size) {
- void* cached = r->elements[ExchangeCurrentSize(current_size_ + 1)];
- Recycler::clear(cached);
- return cached;
+ return static_cast<Result>(
+ r->elements[ExchangeCurrentSize(current_size_ + 1)]);
}
}
Rep* r = rep();
++r->allocated_size;
void*& result = r->elements[ExchangeCurrentSize(current_size_ + 1)];
result = factory(arena);
- return result;
+ return static_cast<Result>(result);
}
-void* RepeatedPtrFieldBase::AddMessageLite(ElementFactory factory) {
- return AddInternal<ElementRecycler<MessageLite>>(factory);
-}
-
-void* RepeatedPtrFieldBase::AddString() {
- return AddInternal<ElementRecycler<std::string>>(NewStringElement);
+void* RepeatedPtrFieldBase::AddOutOfLineHelper(ElementFactory factory) {
+ return AddInternal(factory);
}
void RepeatedPtrFieldBase::CloseGap(int start, int num) {
- if (using_element()) {
+ if (using_sso()) {
if (start == 0 && num == 1) {
tagged_rep_or_elem_ = nullptr;
}
@@ -164,8 +146,7 @@
}
MessageLite* RepeatedPtrFieldBase::AddMessage(const MessageLite* prototype) {
- return static_cast<MessageLite*>(AddInternal<ElementRecycler<MessageLite>>(
- [prototype](Arena* a) { return prototype->New(a); }));
+ return AddInternal([prototype](Arena* a) { return prototype->New(a); });
}
void InternalOutOfLineDeleteMessageLite(MessageLite* message) {
@@ -216,7 +197,6 @@
ABSL_DCHECK(typeid(*src[i]) == typeid(*src[0]))
<< typeid(*src[i]).name() << " vs " << typeid(*src[0]).name();
#endif
- dst[i]->Clear();
dst[i]->CheckTypeAndMergeFrom(*src[i]);
}
return count;
diff --git a/src/google/protobuf/repeated_ptr_field.h b/src/google/protobuf/repeated_ptr_field.h
index 05bb789..644bdb4 100644
--- a/src/google/protobuf/repeated_ptr_field.h
+++ b/src/google/protobuf/repeated_ptr_field.h
@@ -116,7 +116,7 @@
template <typename Handler>
using Value = typename Handler::Type;
- static constexpr int kInlinedCapacity = 1;
+ static constexpr int kSSOCapacity = 1;
using ElementFactory = void* (*)(Arena*);
@@ -158,7 +158,7 @@
//
// * prefer `SizeAtCapacity()` to `size() == Capacity()`;
// * prefer `AllocatedSizeAtCapacity()` to `allocated_size() == Capacity()`.
- int Capacity() const { return capacity_proxy_ + kInlinedCapacity; }
+ int Capacity() const { return capacity_proxy_ + kSSOCapacity; }
template <typename TypeHandler>
const Value<TypeHandler>& at(int index) const {
@@ -183,10 +183,7 @@
template <typename Handler>
Value<Handler>* Add() {
- if (std::is_same<Value<Handler>, std::string>{}) {
- return cast<Handler>(AddString());
- }
- return cast<Handler>(AddMessageLite(Handler::GetNewFunc()));
+ return cast<Handler>(AddOutOfLineHelper(Handler::GetNewFunc()));
}
template <
@@ -199,7 +196,7 @@
return;
}
MaybeExtend();
- if (!using_element()) ++rep()->allocated_size;
+ if (!using_sso()) ++rep()->allocated_size;
auto* result = TypeHandler::New(arena_, std::move(value));
element_at(ExchangeCurrentSize(current_size_ + 1)) = result;
}
@@ -221,14 +218,15 @@
for (int i = 0; i < n; i++) {
Delete<H>(elems[i], nullptr);
}
- if (!using_element()) {
+ if (!using_sso()) {
internal::SizedDelete(rep(),
Capacity() * sizeof(elems[0]) + kRepHeaderSize);
}
}
inline bool NeedsDestroy() const {
- // tagged_rep_or_elem_ contains either allocated element or allocated `Rep`.
+ // Either there is an allocated element in SSO buffer or there is an
+ // allocated Rep.
return tagged_rep_or_elem_ != nullptr;
}
void DestroyProtos();
@@ -251,7 +249,15 @@
// Pre-condition: prototype must not be nullptr.
MessageLite* AddMessage(const MessageLite* prototype);
- void Clear() { ExchangeCurrentSize(0); }
+ template <typename TypeHandler>
+ void Clear() {
+ const int n = current_size_;
+ ABSL_DCHECK_GE(n, 0);
+ if (n > 0) {
+ using H = CommonHandler<TypeHandler>;
+ ClearNonEmpty<H>();
+ }
+ }
// Appends all message values from `from` to this instance.
template <typename T>
@@ -288,21 +294,24 @@
ABSL_DCHECK_EQ(current_size_, allocated_size());
MaybeExtend();
element_at(current_size_++) = value;
- if (!using_element()) ++rep()->allocated_size;
+ if (!using_sso()) ++rep()->allocated_size;
}
protected:
+ template <typename TypeHandler>
void RemoveLast() {
ABSL_DCHECK_GT(current_size_, 0);
ExchangeCurrentSize(current_size_ - 1);
+ using H = CommonHandler<TypeHandler>;
+ H::Clear(cast<H>(element_at(current_size_)));
}
template <typename TypeHandler>
void CopyFrom(const RepeatedPtrFieldBase& other) {
if (&other == this) return;
- RepeatedPtrFieldBase::Clear();
+ RepeatedPtrFieldBase::Clear<TypeHandler>();
if (other.empty()) return;
- RepeatedPtrFieldBase::MergeFrom<Value<TypeHandler>>(other);
+ RepeatedPtrFieldBase::MergeFrom<typename TypeHandler::Type>(other);
}
void CloseGap(int start, int num);
@@ -357,7 +366,7 @@
template <typename TypeHandler>
PROTOBUF_NOINLINE size_t SpaceUsedExcludingSelfLong() const {
size_t allocated_bytes =
- using_element()
+ using_sso()
? 0
: static_cast<size_t>(Capacity()) * sizeof(void*) + kRepHeaderSize;
const int n = allocated_size();
@@ -371,15 +380,15 @@
// Advanced memory management --------------------------------------
- // Returns a pointer to a cleared object ready to reuse if there is a spare
- // allocated object or nullptr otherwise.
+ // Like Add(), but if there are no cleared objects to use, returns nullptr.
template <typename TypeHandler>
Value<TypeHandler>* AddFromCleared() {
- if (ClearedCount() == 0) return nullptr;
- auto* value =
- cast<TypeHandler>(element_at(ExchangeCurrentSize(current_size_ + 1)));
- CommonHandler<TypeHandler>::Clear(value);
- return value;
+ if (current_size_ < allocated_size()) {
+ return cast<TypeHandler>(
+ element_at(ExchangeCurrentSize(current_size_ + 1)));
+ } else {
+ return nullptr;
+ }
}
template <typename TypeHandler>
@@ -401,7 +410,7 @@
elems[allocated_size()] = elems[current_size_];
}
elems[ExchangeCurrentSize(current_size_ + 1)] = value;
- if (!using_element()) ++rep()->allocated_size;
+ if (!using_sso()) ++rep()->allocated_size;
}
template <typename TypeHandler>
@@ -409,24 +418,24 @@
ABSL_DCHECK_NE(value, nullptr);
// Make room for the new pointer.
if (SizeAtCapacity()) {
- // The array is completely full, so grow it.
+ // The array is completely full with no cleared objects, so grow it.
InternalExtend(1);
++rep()->allocated_size;
} else if (AllocatedSizeAtCapacity()) {
// There is no more space in the pointer array because it contains some
- // objects awaiting reuse. We don't want to grow the array in
+ // cleared objects awaiting reuse. We don't want to grow the array in
// this case because otherwise a loop calling AddAllocated() followed by
// Clear() would leak memory.
using H = CommonHandler<TypeHandler>;
Delete<H>(element_at(current_size_), arena_);
} else if (current_size_ < allocated_size()) {
- // We have some unused allocated objects. Their order is not important,
- // so we move the first one to the end to make room for the pointer.
+ // We have some cleared objects. We don't care about their order, so we
+ // can just move the first one to the end to make space.
element_at(allocated_size()) = element_at(current_size_);
++rep()->allocated_size;
} else {
- // There are no unused allocated objects.
- if (!using_element()) ++rep()->allocated_size;
+ // There are no cleared objects.
+ if (!using_sso()) ++rep()->allocated_size;
}
element_at(ExchangeCurrentSize(current_size_ + 1)) = value;
@@ -455,13 +464,13 @@
ABSL_DCHECK_GT(current_size_, 0);
ExchangeCurrentSize(current_size_ - 1);
auto* result = cast<TypeHandler>(element_at(current_size_));
- if (using_element()) {
+ if (using_sso()) {
tagged_rep_or_elem_ = nullptr;
} else {
--rep()->allocated_size;
if (current_size_ < allocated_size()) {
- // There are unused allocated elements on the end; replace the removed
- // element with the last allocated element.
+ // There are cleared elements on the end; replace the removed element
+ // with the last allocated element.
element_at(current_size_) = element_at(allocated_size());
}
}
@@ -477,7 +486,7 @@
ABSL_DCHECK(TypeHandler::GetArena(value) == nullptr)
<< "AddCleared() can only accept values not on an arena.";
MaybeExtend();
- if (using_element()) {
+ if (using_sso()) {
tagged_rep_or_elem_ = value;
} else {
element_at(rep()->allocated_size++) = value;
@@ -491,14 +500,13 @@
<< "an arena.";
ABSL_DCHECK(tagged_rep_or_elem_ != nullptr);
ABSL_DCHECK_GT(allocated_size(), current_size_);
- void* result;
- if (using_element()) {
- result = std::exchange(tagged_rep_or_elem_, nullptr);
+ if (using_sso()) {
+ auto* result = cast<TypeHandler>(tagged_rep_or_elem_);
+ tagged_rep_or_elem_ = nullptr;
+ return result;
} else {
- result = element_at(--rep()->allocated_size);
+ return cast<TypeHandler>(element_at(--rep()->allocated_size));
}
- TypeHandler::Clear(cast<TypeHandler>(result));
- return cast<TypeHandler>(result);
}
// Slowpath handles all cases, copying if necessary.
@@ -536,7 +544,7 @@
// than three times.
RepeatedPtrFieldBase temp(other->GetArena());
if (!this->empty()) {
- temp.MergeFrom<Value<TypeHandler>>(*this);
+ temp.MergeFrom<typename TypeHandler::Type>(*this);
}
this->CopyFrom<TypeHandler>(*other);
other->InternalSwap(&temp);
@@ -617,29 +625,30 @@
ABSL_DCHECK_LE(size(), allocated_size());
ABSL_DCHECK_LE(allocated_size(), Capacity());
// This is equivalent to `current_size_ == Capacity()`.
- //
- // Using less than instead of equality gives compiler an opportunity to
- // generate less instructions.
+ // Assuming `Capacity()` function is inlined, compiler is likely to optimize
+ // away "+ kSSOCapacity" and reduce it to "current_size_ > capacity_proxy_"
+ // which is an instruction less than "current_size_ == capacity_proxy_ + 1".
return current_size_ >= Capacity();
}
inline bool AllocatedSizeAtCapacity() const {
// Harden invariant size() <= allocated_size() <= Capacity().
ABSL_DCHECK_LE(size(), allocated_size());
ABSL_DCHECK_LE(allocated_size(), Capacity());
- // See comment in SizeAtCapacity().
- return using_element() ? (tagged_rep_or_elem_ != nullptr)
- : rep()->allocated_size >= Capacity();
+ // This combines optimization mentioned in `SizeAtCapacity()` and simplifies
+ // `allocated_size()` in sso case.
+ return using_sso() ? (tagged_rep_or_elem_ != nullptr)
+ : rep()->allocated_size >= Capacity();
}
void* const* elements() const {
- return using_element() ? &tagged_rep_or_elem_ : +rep()->elements;
+ return using_sso() ? &tagged_rep_or_elem_ : +rep()->elements;
}
void** elements() {
- return using_element() ? &tagged_rep_or_elem_ : +rep()->elements;
+ return using_sso() ? &tagged_rep_or_elem_ : +rep()->elements;
}
void*& element_at(int index) {
- if (using_element()) {
+ if (using_sso()) {
ABSL_DCHECK_EQ(index, 0);
return tagged_rep_or_elem_;
}
@@ -650,11 +659,11 @@
}
int allocated_size() const {
- return using_element() ? (tagged_rep_or_elem_ != nullptr ? 1 : 0)
- : rep()->allocated_size;
+ return using_sso() ? (tagged_rep_or_elem_ != nullptr ? 1 : 0)
+ : rep()->allocated_size;
}
Rep* rep() {
- ABSL_DCHECK(!using_element());
+ ABSL_DCHECK(!using_sso());
return reinterpret_cast<Rep*>(
reinterpret_cast<uintptr_t>(tagged_rep_or_elem_) - 1);
}
@@ -662,7 +671,7 @@
return const_cast<RepeatedPtrFieldBase*>(this)->rep();
}
- bool using_element() const {
+ bool using_sso() const {
return (reinterpret_cast<uintptr_t>(tagged_rep_or_elem_) & 1) == 0;
}
@@ -679,13 +688,28 @@
TypeHandler::Delete(cast<TypeHandler>(obj), arena);
}
- // Merges messages from `from` into available, allocated messages sitting in
- // the range `[size(), allocated_size())`. Returns the number of message
- // merged which is `ClearedCount(), from.size())`.
- // Note that this function does explicitly NOT update `current_size_`. This
- // function is out of line as it should be the slow path: this scenario only
- // happens when a caller constructs and fills a repeated field, then shrinks
- // it, and then merges additional messages into it.
+ // Out-of-line helper routine for Clear() once the inlined check has
+ // determined the container is non-empty
+ template <typename TypeHandler>
+ PROTOBUF_NOINLINE void ClearNonEmpty() {
+ const int n = current_size_;
+ void* const* elems = elements();
+ int i = 0;
+ ABSL_DCHECK_GT(n, 0);
+ // do/while loop to avoid initial test because we know n > 0
+ do {
+ TypeHandler::Clear(cast<TypeHandler>(elems[i++]));
+ } while (i < n);
+ ExchangeCurrentSize(0);
+ }
+
+ // Merges messages from `from` into available, cleared messages sitting in the
+ // range `[size(), allocated_size())`. Returns the number of message merged
+ // which is `ClearedCount(), from.size())`.
+ // Note that this function does explicitly NOT update `current_size_`.
+ // This function is out of line as it should be the slow path: this scenario
+ // only happens when a caller constructs and fills a repeated field, then
+ // shrinks it, and then merges additional messages into it.
int MergeIntoClearedMessages(const RepeatedPtrFieldBase& from);
// Appends all messages from `from` to this instance, using the
@@ -712,55 +736,40 @@
// Returns a pointer to the element directly beyond the last element.
inline void** InternalReserve(int n) {
if (n <= Capacity()) {
- return elements() + current_size_;
+ void** elements = using_sso() ? &tagged_rep_or_elem_ : rep()->elements;
+ return elements + current_size_;
}
return InternalExtend(n - Capacity());
}
- // Internal helpers for Add that keep definition out-of-line.
- void* AddMessageLite(ElementFactory factory);
- void* AddString();
+ // Internal helper for Add that keeps definition out-of-line.
+ void* AddOutOfLineHelper(ElementFactory factory);
// Common implementation used by various Add* methods. `factory` is an object
- // used to construct a new element unless there are spare allocated elements
+ // used to construct a new element unless there are spare cleared elements
// ready for reuse. Returns pointer to the new element.
//
// Note: avoid inlining this function in methods such as `Add()` as this would
// drastically increase binary size due to template instantiation and implicit
- // inlining.
- template <typename Recycler, typename Factory>
- void* AddInternal(Factory factory);
+ // inlining. Instead, use wrapper functions with out-of-line definition
+ // similar to `AddOutOfLineHelper`.
+ template <typename F>
+ auto* AddInternal(F factory);
// A few notes on internal representation:
//
- // * Class layout is optimized to minimize the size: 24 bytes on x86-64.
- // * The elements can be stored in one of the two ways and `using_element()`
- // tells which one is currently used.
- //
- // In case of using_element():
- //
- // tagged_rep_or_elem_ is a storage for at most one pointer.
- // Number of allocated objects (0 or 1) is determined whether
- // tagged_rep_or_elem_ is nullptr.
- //
- // Otherwise,
- //
- // tagged_rep_or_elem_ is tagged (LSB is 1) pointer to `Rep`, where
- // `Rep` contains number of allocated objects as well as the buffer with
- // pointers to allocated elements. Rep allows to (a) keep the sizeof small
- // (b) allocate both buffer for elements and an integer with allocated
- // objects count in one shot.
- //
- // In both cases, RepeatedPtrFieldBase may own allocated but unused objects:
- //
- // 1. Their count is determined by `ClearedCount()`.
- // 2. Pointers to them are stored directly after pointers to used objects.
- // 3. They can be reused in order to avoid extra allocation (note that in
- // some cases these objects need to be cleared with `TypeHandler::Clear`
- // before they can be reused).
+ // We use an indirected approach, with struct Rep, to keep
+ // sizeof(RepeatedPtrFieldBase) equivalent to what it was before arena support
+ // was added; namely, 3 8-byte machine words on x86-64. An instance of Rep is
+ // allocated only when the repeated field is non-empty, and it is a
+ // dynamically-sized struct (the header is directly followed by elements[]).
+ // We place arena_ and current_size_ directly in the object to avoid cache
+ // misses due to the indirection, because these fields are checked frequently.
+ // Placing all fields directly in the RepeatedPtrFieldBase instance would cost
+ // significant performance for memory-sensitive workloads.
void* tagged_rep_or_elem_;
int current_size_;
- int capacity_proxy_; // See `Capacity()`
+ int capacity_proxy_; // we store `capacity - kSSOCapacity` as an optimization
Arena* arena_;
};
@@ -975,7 +984,7 @@
pointer Mutable(int index) ABSL_ATTRIBUTE_LIFETIME_BOUND;
// Unlike std::vector, adding an element to a RepeatedPtrField doesn't always
- // make a new element; it might reuse an element left over from when the
+ // make a new element; it might re-use an element left over from when the
// field was Clear()'d or resize()'d smaller. For this reason, Add() is the
// fastest API for adding a new element.
pointer Add() ABSL_ATTRIBUTE_LIFETIME_BOUND;
@@ -1157,9 +1166,9 @@
void UnsafeArenaExtractSubrange(int start, int num, Element** elements);
// When elements are removed by calls to RemoveLast() or Clear(), they
- // are not actually freed. Instead, they are kept so that they can be reused
- // later. This can save lots of CPU time when repeatedly reusing a protocol
- // message for similar purposes.
+ // are not actually freed. Instead, they are cleared and kept so that
+ // they can be reused later. This can save lots of CPU time when
+ // repeatedly reusing a protocol message for similar purposes.
//
// Hardcore programs may choose to manipulate these cleared objects
// to better optimize memory management using the following routines.
@@ -1375,7 +1384,7 @@
template <typename Element>
inline void RepeatedPtrField<Element>::RemoveLast() {
- RepeatedPtrFieldBase::RemoveLast();
+ RepeatedPtrFieldBase::RemoveLast<TypeHandler>();
}
template <typename Element>
@@ -1450,7 +1459,7 @@
template <typename Element>
inline void RepeatedPtrField<Element>::Clear() {
- RepeatedPtrFieldBase::Clear();
+ RepeatedPtrFieldBase::Clear<TypeHandler>();
}
template <typename Element>