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));
}
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>
static void destroy(Alloc *alloc, slot_type *slot) {
absl::allocator_traits<Alloc>::destroy(*alloc, slot);

@ -14,11 +14,14 @@
#include "absl/container/btree_test.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <functional>
#include <limits>
#include <map>
#include <memory>
#include <numeric>
#include <stdexcept>
#include <string>
#include <type_traits>
@ -1291,7 +1294,7 @@ TEST(Btree, BtreeMapCanHoldMoveOnlyTypes) {
std::unique_ptr<std::string> &v = m["A"];
EXPECT_TRUE(v == nullptr);
v.reset(new std::string("X"));
v = absl::make_unique<std::string>("X");
auto iter = m.find("A");
EXPECT_EQ("X", *iter->second);
@ -3081,6 +3084,110 @@ TEST(Btree, InvalidIteratorUse) {
}
#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 container_internal
ABSL_NAMESPACE_END

@ -1196,7 +1196,9 @@ class btree_iterator {
}
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 {
#ifdef ABSL_BTREE_ENABLE_GENERATIONS
@ -1225,7 +1227,6 @@ template <typename Params>
class btree {
using node_type = btree_node<Params>;
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;
// 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;
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
// 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,
@ -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.
auto iter = other.begin();
if (iter == other.end()) return;
insert_multi(maybe_move_from_iterator(iter));
insert_multi(iter.slot());
++iter;
for (; iter != other.end(); ++iter) {
// If the btree is not empty, we can just insert the new value at the end
// 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>
void btree<P>::insert_iterator_unique(InputIterator b, InputIterator e, char) {
for (; b != e; ++b) {
init_type value(*b);
insert_hint_unique(end(), params_type::key(value), std::move(value));
// Use a node handle to manage a temp slot.
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.
node_type extract(iterator position) {
// Use Move instead of Transfer, because the rebalancing code expects to
// have a valid object to scribble metadata bits on top of.
auto node = CommonAccess::Move<node_type>(get_allocator(), position.slot());
// Use Construct instead of Transfer because the rebalancing code will
// destroy the slot later.
auto node =
CommonAccess::Construct<node_type>(get_allocator(), position.slot());
erase(position);
return node;
}
@ -291,8 +292,11 @@ class btree_set_container : public btree_container<Tree> {
}
template <typename... Args>
std::pair<iterator, bool> emplace(Args &&... args) {
init_type v(std::forward<Args>(args)...);
return this->tree_.insert_unique(params_type::key(v), std::move(v));
// 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_.insert_unique(params_type::key(slot), slot);
}
iterator insert(const_iterator hint, const value_type &v) {
return this->tree_
@ -306,9 +310,12 @@ class btree_set_container : public btree_container<Tree> {
}
template <typename... 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_
.insert_hint_unique(iterator(hint), params_type::key(v), std::move(v))
.insert_hint_unique(iterator(hint), params_type::key(slot), slot)
.first;
}
template <typename InputIterator>
@ -598,12 +605,18 @@ class btree_multiset_container : public btree_container<Tree> {
}
template <typename... 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>
iterator emplace_hint(const_iterator hint, Args &&... args) {
return this->tree_.insert_hint_multi(
iterator(hint), 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_hint_multi(iterator(hint),
CommonAccess::GetSlot(node));
}
iterator insert(node_type &&node) {
if (!node) return this->end();

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

Loading…
Cancel
Save