Allow for using b-tree with `value_type`s that can only be constructed by the allocator (ignoring copy/move constructors).

We were using `init_type`s for temp values that we would move into slots, but in this case, we need to have actual slots. We use node handles for managing slots outside of nodes.

Also, in btree::copy_or_move_values_in_order, pass the slots from the iterators rather than references to values. This allows for moving from map keys instead of copying for standard layout types.

In the test, fix a couple of ClangTidy warnings from missing includes and calling `new` instead of `make_unique`.

PiperOrigin-RevId: 452062967
Change-Id: I870e89ae1aa5b3cfa62ae6e75b73ffc3d52e731c
pull/1194/head
Evan Brown 3 years ago committed by Copybara-Service
parent a4cc270df1
commit aeb2dc9c34
  1. 5
      absl/container/btree_set.h
  2. 109
      absl/container/btree_test.cc
  3. 24
      absl/container/internal/btree.h
  4. 33
      absl/container/internal/btree_container.h
  5. 11
      absl/container/internal/common.h
  6. 9
      absl/container/internal/container_memory.h

@ -751,6 +751,11 @@ struct set_slot_policy {
absl::allocator_traits<Alloc>::construct(*alloc, slot, std::move(*other)); absl::allocator_traits<Alloc>::construct(*alloc, slot, std::move(*other));
} }
template <typename Alloc>
static void construct(Alloc *alloc, slot_type *slot, const slot_type *other) {
absl::allocator_traits<Alloc>::construct(*alloc, slot, *other);
}
template <typename Alloc> template <typename Alloc>
static void destroy(Alloc *alloc, slot_type *slot) { static void destroy(Alloc *alloc, slot_type *slot) {
absl::allocator_traits<Alloc>::destroy(*alloc, slot); absl::allocator_traits<Alloc>::destroy(*alloc, slot);

@ -14,11 +14,14 @@
#include "absl/container/btree_test.h" #include "absl/container/btree_test.h"
#include <algorithm>
#include <array>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <limits> #include <limits>
#include <map> #include <map>
#include <memory> #include <memory>
#include <numeric>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@ -1291,7 +1294,7 @@ TEST(Btree, BtreeMapCanHoldMoveOnlyTypes) {
std::unique_ptr<std::string> &v = m["A"]; std::unique_ptr<std::string> &v = m["A"];
EXPECT_TRUE(v == nullptr); EXPECT_TRUE(v == nullptr);
v.reset(new std::string("X")); v = absl::make_unique<std::string>("X");
auto iter = m.find("A"); auto iter = m.find("A");
EXPECT_EQ("X", *iter->second); EXPECT_EQ("X", *iter->second);
@ -3081,6 +3084,110 @@ TEST(Btree, InvalidIteratorUse) {
} }
#endif #endif
class OnlyConstructibleByAllocator {
explicit OnlyConstructibleByAllocator(int i) : i_(i) {}
public:
OnlyConstructibleByAllocator(const OnlyConstructibleByAllocator &other)
: i_(other.i_) {}
OnlyConstructibleByAllocator &operator=(
const OnlyConstructibleByAllocator &other) {
i_ = other.i_;
return *this;
}
int Get() const { return i_; }
bool operator==(int i) const { return i_ == i; }
private:
template <typename T>
friend class OnlyConstructibleAllocator;
int i_;
};
template <typename T = OnlyConstructibleByAllocator>
class OnlyConstructibleAllocator : public std::allocator<T> {
public:
OnlyConstructibleAllocator() = default;
template <class U>
explicit OnlyConstructibleAllocator(const OnlyConstructibleAllocator<U> &) {}
void construct(OnlyConstructibleByAllocator *p, int i) {
new (p) OnlyConstructibleByAllocator(i);
}
template <typename Pair>
void construct(Pair *p, const int i) {
OnlyConstructibleByAllocator only(i);
new (p) Pair(std::move(only), i);
}
template <class U>
struct rebind {
using other = OnlyConstructibleAllocator<U>;
};
};
struct OnlyConstructibleByAllocatorComp {
using is_transparent = void;
bool operator()(OnlyConstructibleByAllocator a,
OnlyConstructibleByAllocator b) const {
return a.Get() < b.Get();
}
bool operator()(int a, OnlyConstructibleByAllocator b) const {
return a < b.Get();
}
bool operator()(OnlyConstructibleByAllocator a, int b) const {
return a.Get() < b;
}
};
TEST(Btree, OnlyConstructibleByAllocatorType) {
const std::array<int, 2> arr = {3, 4};
{
absl::btree_set<OnlyConstructibleByAllocator,
OnlyConstructibleByAllocatorComp,
OnlyConstructibleAllocator<>>
set;
set.emplace(1);
set.emplace_hint(set.end(), 2);
set.insert(arr.begin(), arr.end());
EXPECT_THAT(set, ElementsAre(1, 2, 3, 4));
}
{
absl::btree_multiset<OnlyConstructibleByAllocator,
OnlyConstructibleByAllocatorComp,
OnlyConstructibleAllocator<>>
set;
set.emplace(1);
set.emplace_hint(set.end(), 2);
// TODO(ezb): fix insert_multi to allow this to compile.
// set.insert(arr.begin(), arr.end());
EXPECT_THAT(set, ElementsAre(1, 2));
}
{
absl::btree_map<OnlyConstructibleByAllocator, int,
OnlyConstructibleByAllocatorComp,
OnlyConstructibleAllocator<>>
map;
map.emplace(1);
map.emplace_hint(map.end(), 2);
map.insert(arr.begin(), arr.end());
EXPECT_THAT(map,
ElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3), Pair(4, 4)));
}
{
absl::btree_multimap<OnlyConstructibleByAllocator, int,
OnlyConstructibleByAllocatorComp,
OnlyConstructibleAllocator<>>
map;
map.emplace(1);
map.emplace_hint(map.end(), 2);
// TODO(ezb): fix insert_multi to allow this to compile.
// map.insert(arr.begin(), arr.end());
EXPECT_THAT(map, ElementsAre(Pair(1, 1), Pair(2, 2)));
}
}
} // namespace } // namespace
} // namespace container_internal } // namespace container_internal
ABSL_NAMESPACE_END ABSL_NAMESPACE_END

@ -1196,7 +1196,9 @@ class btree_iterator {
} }
const key_type &key() const { return node_->key(position_); } const key_type &key() const { return node_->key(position_); }
slot_type *slot() { return node_->slot(position_); } decltype(std::declval<Node *>()->slot(0)) slot() {
return node_->slot(position_);
}
void assert_valid_generation() const { void assert_valid_generation() const {
#ifdef ABSL_BTREE_ENABLE_GENERATIONS #ifdef ABSL_BTREE_ENABLE_GENERATIONS
@ -1225,7 +1227,6 @@ template <typename Params>
class btree { class btree {
using node_type = btree_node<Params>; using node_type = btree_node<Params>;
using is_key_compare_to = typename Params::is_key_compare_to; using is_key_compare_to = typename Params::is_key_compare_to;
using init_type = typename Params::init_type;
using field_type = typename node_type::field_type; using field_type = typename node_type::field_type;
// We use a static empty node for the root/leftmost/rightmost of empty btrees // We use a static empty node for the root/leftmost/rightmost of empty btrees
@ -1309,14 +1310,6 @@ class btree {
using slot_type = typename Params::slot_type; using slot_type = typename Params::slot_type;
private: private:
// For use in copy_or_move_values_in_order.
const value_type &maybe_move_from_iterator(const_iterator it) { return *it; }
value_type &&maybe_move_from_iterator(iterator it) {
// This is a destructive operation on the other container so it's safe for
// us to const_cast and move from the keys here even if it's a set.
return std::move(const_cast<value_type &>(*it));
}
// Copies or moves (depending on the template parameter) the values in // Copies or moves (depending on the template parameter) the values in
// other into this btree in their order in other. This btree must be empty // other into this btree in their order in other. This btree must be empty
// before this method is called. This method is used in copy construction, // before this method is called. This method is used in copy construction,
@ -2063,12 +2056,12 @@ void btree<P>::copy_or_move_values_in_order(Btree &other) {
// values is the same order we'll store them in. // values is the same order we'll store them in.
auto iter = other.begin(); auto iter = other.begin();
if (iter == other.end()) return; if (iter == other.end()) return;
insert_multi(maybe_move_from_iterator(iter)); insert_multi(iter.slot());
++iter; ++iter;
for (; iter != other.end(); ++iter) { for (; iter != other.end(); ++iter) {
// If the btree is not empty, we can just insert the new value at the end // If the btree is not empty, we can just insert the new value at the end
// of the tree. // of the tree.
internal_emplace(end(), maybe_move_from_iterator(iter)); internal_emplace(end(), iter.slot());
} }
} }
@ -2205,8 +2198,11 @@ template <typename P>
template <typename InputIterator> template <typename InputIterator>
void btree<P>::insert_iterator_unique(InputIterator b, InputIterator e, char) { void btree<P>::insert_iterator_unique(InputIterator b, InputIterator e, char) {
for (; b != e; ++b) { for (; b != e; ++b) {
init_type value(*b); // Use a node handle to manage a temp slot.
insert_hint_unique(end(), params_type::key(value), std::move(value)); auto node_handle =
CommonAccess::Construct<node_handle_type>(get_allocator(), *b);
slot_type *slot = CommonAccess::GetSlot(node_handle);
insert_hint_unique(end(), params_type::key(slot), slot);
} }
} }

@ -166,9 +166,10 @@ class btree_container {
// Extract routines. // Extract routines.
node_type extract(iterator position) { node_type extract(iterator position) {
// Use Move instead of Transfer, because the rebalancing code expects to // Use Construct instead of Transfer because the rebalancing code will
// have a valid object to scribble metadata bits on top of. // destroy the slot later.
auto node = CommonAccess::Move<node_type>(get_allocator(), position.slot()); auto node =
CommonAccess::Construct<node_type>(get_allocator(), position.slot());
erase(position); erase(position);
return node; return node;
} }
@ -291,8 +292,11 @@ class btree_set_container : public btree_container<Tree> {
} }
template <typename... Args> template <typename... Args>
std::pair<iterator, bool> emplace(Args &&... args) { std::pair<iterator, bool> emplace(Args &&... args) {
init_type v(std::forward<Args>(args)...); // Use a node handle to manage a temp slot.
return this->tree_.insert_unique(params_type::key(v), std::move(v)); auto node = CommonAccess::Construct<node_type>(this->get_allocator(),
std::forward<Args>(args)...);
auto *slot = CommonAccess::GetSlot(node);
return this->tree_.insert_unique(params_type::key(slot), slot);
} }
iterator insert(const_iterator hint, const value_type &v) { iterator insert(const_iterator hint, const value_type &v) {
return this->tree_ return this->tree_
@ -306,9 +310,12 @@ class btree_set_container : public btree_container<Tree> {
} }
template <typename... Args> template <typename... Args>
iterator emplace_hint(const_iterator hint, Args &&... args) { iterator emplace_hint(const_iterator hint, Args &&... args) {
init_type v(std::forward<Args>(args)...); // Use a node handle to manage a temp slot.
auto node = CommonAccess::Construct<node_type>(this->get_allocator(),
std::forward<Args>(args)...);
auto *slot = CommonAccess::GetSlot(node);
return this->tree_ return this->tree_
.insert_hint_unique(iterator(hint), params_type::key(v), std::move(v)) .insert_hint_unique(iterator(hint), params_type::key(slot), slot)
.first; .first;
} }
template <typename InputIterator> template <typename InputIterator>
@ -598,12 +605,18 @@ class btree_multiset_container : public btree_container<Tree> {
} }
template <typename... Args> template <typename... Args>
iterator emplace(Args &&... args) { iterator emplace(Args &&... args) {
return this->tree_.insert_multi(init_type(std::forward<Args>(args)...)); // Use a node handle to manage a temp slot.
auto node = CommonAccess::Construct<node_type>(this->get_allocator(),
std::forward<Args>(args)...);
return this->tree_.insert_multi(CommonAccess::GetSlot(node));
} }
template <typename... Args> template <typename... Args>
iterator emplace_hint(const_iterator hint, Args &&... args) { iterator emplace_hint(const_iterator hint, Args &&... args) {
return this->tree_.insert_hint_multi( // Use a node handle to manage a temp slot.
iterator(hint), init_type(std::forward<Args>(args)...)); auto node = CommonAccess::Construct<node_type>(this->get_allocator(),
std::forward<Args>(args)...);
return this->tree_.insert_hint_multi(iterator(hint),
CommonAccess::GetSlot(node));
} }
iterator insert(node_type &&node) { iterator insert(node_type &&node) {
if (!node) return this->end(); if (!node) return this->end();

@ -84,10 +84,11 @@ class node_handle_base {
PolicyTraits::transfer(alloc(), slot(), s); PolicyTraits::transfer(alloc(), slot(), s);
} }
struct move_tag_t {}; struct construct_tag_t {};
node_handle_base(move_tag_t, const allocator_type& a, slot_type* s) template <typename... Args>
node_handle_base(construct_tag_t, const allocator_type& a, Args&&... args)
: alloc_(a) { : alloc_(a) {
PolicyTraits::construct(alloc(), slot(), s); PolicyTraits::construct(alloc(), slot(), std::forward<Args>(args)...);
} }
void destroy() { void destroy() {
@ -186,8 +187,8 @@ struct CommonAccess {
} }
template <typename T, typename... Args> template <typename T, typename... Args>
static T Move(Args&&... args) { static T Construct(Args&&... args) {
return T(typename T::move_tag_t{}, std::forward<Args>(args)...); return T(typename T::construct_tag_t{}, std::forward<Args>(args)...);
} }
}; };

@ -402,6 +402,15 @@ struct map_slot_policy {
} }
} }
// Construct this slot by copying from another slot.
template <class Allocator>
static void construct(Allocator* alloc, slot_type* slot,
const slot_type* other) {
emplace(slot);
absl::allocator_traits<Allocator>::construct(*alloc, &slot->value,
other->value);
}
template <class Allocator> template <class Allocator>
static void destroy(Allocator* alloc, slot_type* slot) { static void destroy(Allocator* alloc, slot_type* slot) {
if (kMutableKeys::value) { if (kMutableKeys::value) {

Loading…
Cancel
Save