folly/folly/container/heap_vector_types.h

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * 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
 *
 *     http://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.
 */

/*
 * This header defines two new containers, heap_vector_set and heap_vector_map
 * classes. These containers are designed to be a drop-in replacement of
 * sorted_vector_set and sorted_vector_map. Similarly to sorted_vector_map/set,
 * heap_vector_map/set models AssociativeContainers. Below, we list important
 * differences from std::set and std::map (also documented in
 * folly/sorted_vector_types.h):
 *
 *   - insert() and erase() invalidate iterators and references.
 *   - erase(iterator) returns an iterator pointing to the next valid element.
 *   - insert() and erase() are O(N)
 *   - our iterators model RandomAccessIterator
 *   - heap_vector_map::value_type is pair<K,V>, not pair<const K,V>.
 *     (This is basically because we want to store the value_type in
 *     std::vector<>, which requires it to be Assignable.)
 *   - insert() single key variants, emplace(), and emplace_hint() only provide
 *     the strong exception guarantee (unchanged when exception is thrown) when
 *     std::is_nothrow_move_constructible<value_type>::value is true.
 *
 * heap_vector_map/set have exactly the same size as sorted_vector_map/set.
 * These containers utilizes a vector container (e.g. std::vector) to store the
 * values. Heap containers (similarly to sorted containers) have no additional
 * memory overhead. They lay out the data in an optimal way w.r.t locality (see
 * https://algorithmica.org/en/eytzinger), called eytzinger or heap order. For
 * example in a sorted_vector_set, the underlying vector contains:
 *              index    0   1   2   3   4   5   6   7   8   9
 *       vector[index]   0, 10, 20, 30, 40, 50, 60, 70, 80, 90
 * while in a heap_vector_set, the underlying vector contains:
 *              index    0   1   2   3   4   5   6   7   8   9
 *       vector[index]  60, 30, 80, 10, 50, 70, 90,  0, 20, 40
 * Lookup elements in sorted vector containers relies on binary search,
 * std::lower_bound. While in heap containers, lookup operation has two
 * benefits:
 *
 * 1. Cache locality, the container is traversed sequentially instead of binary
 * search that jumps around the sorted vector.
 * 2. The branches in a binary search are mispredicted resulting in hardware
 * penalty while using heap lookup search the branch can be avoided by using
 * cmov instruction. We observerd look up operations are up to 2X faster than
 * sorted_vector_map.
 *
 * However, Insertion/deletion operations are much slower. If insertions and
 * deletions are rare operations for your use case then heap containers might
 * be the right choice for you. Also, to minimize impact of insertions while
 * creating heap containers, we recommend not to insert element by element
 * instead first collect elements in a vector, then construct the heap map from
 * it.
 *
 * Another substantial trade off, inorder traversal of heap container elements
 * is slower than sorted vector containers. This is expected as heap map needs
 * to jump around to access map elements in order. A remedy is to use underlying
 * vector iterators. This works only when the order is irrelevant. For example,
 * using heap container iterator:
 *         for (auto& e: HeapSet)
 *           std::cout << e << ", ";
 * Prints:  0, 10, 20, 30, 40, 50, 60, 70, 80, 90
 * and using underlying vector container iterator:
 *         for (auto& e : HeapSet.iterate())
 *           std::cout << e << ", ";
 * Prints:  60, 30, 80, 10, 50, 70, 90,  0, 20, 40
 * The latter loop is the fastest traversal.
 *
 * Finally The main benefit of heap containers is a compact representation
 * that achieves fast random lookup. Use this map when lookup is the
 * dominant operation and at the same time saving memory is important.
 */

#pragma once

#include <algorithm>
#include <cassert>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <vector>

#include <folly/Range.h>
#include <folly/ScopeGuard.h>
#include <folly/Traits.h>
#include <folly/Utility.h>
#include <folly/container/Iterator.h>
#include <folly/functional/Invoke.h>
#include <folly/lang/Exception.h>
#include <folly/memory/MemoryResource.h>
#include <folly/portability/Builtins.h>
#include <folly/small_vector.h>

namespace folly {
template <
    typename Key,
    typename Value,
    typename Compare,
    typename Allocator,
    typename GrowthPolicy,
    typename Container>
class heap_vector_map;

namespace detail {

namespace heap_vector_detail {

/*
 * Heap Containers Helper Functions
 * ---------------------------------
 * Terminology:
 * - offset means the index at which element is stored in the container vector,
 *   following the heap order.
 * - index means the element rank following the container compare order.
 *
 * Introduction:
 * Heap order can be constructed from a sorted input vector using below
 * naive algorithm.
 *
 *     heapify(input, output, index = 0, offset = 1) {
 *       if (offset <= size) {
 *         index = heapify(input, output, i, 2 * offset);
 *         output[offset - 1] = input[index++];
 *         index = heapify(input, output, i, 2 * offset + 1);
 *       }
 *       return index;
 *     }
 *
 * Helper functions below implement efficient algorithms to:
 *  - Find offsets of the smallest/greatest elements.
 *  - Given an offset, calculate the offset where next/previous element is
 *  stored if it exists.
 *  - Given a start and an end offsets, calculate distance between their
 *  corresponding indexes.
 *  - Fast inplace heapification.
 *  - Insert a new element while preserving heap order.
 *  - Delete an element and preserve heap order.
 *  - find lower/upper bound of a key following container compare order.
 */

// Returns the offset of the smallest element in the heap container.
// The samllest element is stored at:
//     vector[2^n - 1] where 2^n <= size < 2^(n+1).
// firstOffset returns 2^n - 1 if size > 0, 0 otherwise
template <typename size_type>
size_type firstOffset(size_type size) {
  if (size) {
    return (1
            << (CHAR_BIT * sizeof(unsigned long) -
                __builtin_clzl((unsigned long)size) - 1)) -
        1;
  } else {
    return 0;
  }
}

// Returns the offset of greatest element in heap container.
// the greatest element is storted at:
//     vector[2^n - 2] if 2^n - 1 <= size < 2^(n+1)
// lastOffset returns 2^n - 2 if size > 0, 0 otherwise
template <typename size_type>
size_type lastOffset(size_type size) {
  if (size) {
    return ((size & (size + 1)) == 0 ? size : firstOffset(size)) - 1;
  }
  return 0;
}

// Returns the offset of the next element. It is calculated based on the
// size of the map.
// To simplify implementation, offset must be 1-based
// return value is also 1-based.
template <typename size_type>
size_type next(size_type offset, size_type size) {
  auto next = (2 * offset);
  if (next >= size) {
    return offset >> __builtin_ffsl((unsigned long)~offset);
  } else {
    next += 1;
    for (auto n = 2 * next; n <= size; n *= 2) {
      next = n;
    }
    return next;
  }
}

// Returns the offset of the previous element. It is calculated based on
// the size of the map.
// To simplify implementation, offset must be 1-based
// return value is also 1-based.
template <typename size_type>
size_type prev(size_type offset, size_type size) {
  auto prev = 2 * offset;
  if (prev <= size) {
    for (auto p = 2 * prev + 1; p <= size; p = 2 * p + 1) {
      if (2 * p >= size) {
        return p;
      }
    }
    return prev;
  }
  return offset >> __builtin_ffsl((unsigned long)offset);
}

// To avoid scanning all offsets, skip offsets that cannot be within the
// range of offset1 and offset2.
// Note We could compute least common ancestor of offset1 and offset2
// to further minimize scanning. However it is not profitable when container
// is relatively small.
template <typename size_type>
size_type getStartOffsetToScan(size_type offset1, size_type offset2) {
  while ((offset1 & (offset1 - 1)) != 0) {
    offset1 >>= 1;
  }
  if (offset1 > 1) {
    while ((offset2 & (offset2 - 1)) != 0) {
      offset2 >>= 1;
    }
    offset1 = std::min(offset1, offset2);
  }
  return offset1;
}

// Given a start and end offsets, returns the distance between
// their corresponding indexes.
template <typename Container, typename size_type>
typename Container::difference_type distance(
    Container& cont, size_type start, size_type end) {
  using difference_type = typename Container::difference_type;
  difference_type dist = 0;
  size_type size = cont.size();
  // To simplify logic base start and end from one.
  start++;
  end++;
  std::function<bool(size_type, size_type)> calculateDistance =
      [&](size_type offset, size_type lb) {
        if (offset > size)
          return false;
        for (; offset <= size; offset <<= 1)
          ;
        offset >>= 1;
        for (; offset > lb; offset >>= 1) {
          if (offset == start) {
            if (dist) {
              dist *= -1;
              return true;
            }
            dist = 1;
          } else if (offset == end) {
            if (dist) {
              return true;
            }
            dist = 1;
          } else if (dist) {
            dist++;
          }
          if (calculateDistance(2 * offset + 1, offset)) {
            return true;
          }
        }
        return false;
      };
  auto offset = getStartOffsetToScan(start, end);
  calculateDistance(offset, size_type(0));
  // Handle start == end()
  if (start > size) {
    dist *= -1;
  }

  return dist;
}

// Returns the offset for each index in heap container
// for example if size = 7 then
//       index     0  1  2  3  4  5  6
//     offsets = { 3, 1, 4, 0, 5, 2, 6 }
// The smallest element (index = 0) of heap container is stored at cont[3] and
// so on.
template <typename size_type, typename Offsets>
void getOffsets(size_type size, Offsets& offsets) {
  size_type i = 0;
  size_type offset = 0;
  size_type index = size;
  do {
    for (size_type o = offset; o < size; o = 2 * o + 2) {
      offsets[i++] = o;
    }
    offset = offsets[--i];
    offsets[--index] = offset;
    offset = 2 * offset + 1;
  } while (i || offset < size);
}

// Inplace conversion of a sorted vector to heap layout
// This algorithm utilizes circular swaps to position each element in its heap
// order offset in the vector. For example, given a sorted vector below:
//     cont = { 0, 10, 20, 30, 40, 50, 60, 70 }
// getOffsets returns:
//       index     0  1  2  3  4  5  6  7
//     offsets = { 4, 2, 6, 1, 3, 5, 7, 0 }
//
// The algorithm moves elements circularly:
// cont[4]->cont[0]->cont[7]->cont[6]->cont[2]->cont[1]->cont[3]-> cont[4]
// cont[5] remains inplace
// returns:
// cont = { 40, 20, 60, 10, 30, 50, 70, 0 }
template <class Container>
void heapify(Container& cont) {
  using size_type = typename Container::size_type;
  size_type size = cont.size();
  std::vector<size_type> offsets;
  offsets.resize(size);
  getOffsets(size, offsets);

  std::function<void(size_type, size_type)> rotate = [&](size_type next,
                                                         size_type index) {
    std::vector<size_type> worklist;
    while (index != next) {
      worklist.push_back(next);
      next = offsets[next];
    }
    while (!worklist.empty()) {
      auto cur = worklist.back();
      worklist.pop_back();
      cont[offsets[cur]] = std::move(cont[cur]);
      offsets[cur] = size;
    }
  };

  for (size_type index = 0; index < size; index++) {
    // already moved
    if (offsets[index] == size) {
      continue;
    }
    size_type next = offsets[index];
    if (next == index) {
      continue;
    }
    // Subtlety: operator[] returns a Container::reference. Because
    // Container::reference can be a proxy, using bare `auto` is not
    // sufficient to remove the "reference nature" of
    // Container::reference and force a move out of the container;
    // instead, we need Container::value_type.
    typename Container::value_type tmp = std::move(cont[index]);
    rotate(next, index);
    cont[next] = std::move(tmp);
  }
}

// Below helper functions to implement inplace insertion/deletion.

// Returns the sequence of offsets that need to be moved. This sequence
// is the range between size-1 and the offset of the inserted element.
// There are two cases: {size - 1, ..., offset} or {offset, ..., size - 1}
// For example if 45 is inserted at offset == 0 and size == 9.
// Before insertion:
//     element    0 10   20 30 40     50 60 70
//     offset     7  3    1  4  0      5  2  6
// After inserting 45:
//     element    0 10   20 30 40 45  50 60 70
//     offset     7  3    8  1  4  0   5  2  6
// This function returns:
//     offsets = { 8, 1, 4, 0 }
template <typename size_type, typename Offsets>
bool getOffsetRange(
    size_type size, Offsets& offsets, size_type offset, size_type current) {
  for (; current <= size; current <<= 1) {
    if (getOffsetRange(size, offsets, offset, 2 * current + 1)) {
      return true;
    }
    if (offsets.empty()) {
      if (offset == current || size == current) {
        // Start recording offsets that need to be moved.
        offsets.push_back(current - 1);
      }
    } else {
      // record offset
      offsets.push_back(current - 1);
      if (offset == current || size == current) {
        // Stop recording offsets
        return true;
      }
    }
  }
  return false;
}

template <typename size_type, typename Offsets>
void getOffsetRange(size_type size, Offsets& offsets, size_type offset) {
  auto start = getStartOffsetToScan(offset, size);
  getOffsetRange(size, offsets, offset, start);
}

// Insert a new element in heap order
// Assumption: the inserted element is already pushed at the back of the
// container vector (i.e. located at vector[size - 1]).
template <typename size_type, class Container>
size_type insert(size_type offset, Container& cont) {
  size_type size = cont.size();
  if (size == 1) {
    return 0;
  }
  size_type adjust = 1;
  if (offset == size - 1) {
    adjust = 0;
    auto last = lastOffset(size);
    if (last == offset) {
      return offset;
    }
    offset = last;
  }
  std::vector<size_type> offsets;
  offsets.reserve(size);
  getOffsetRange(size, offsets, offset + 1);
  typename Container::value_type v = std::move(cont[size - 1]);
  if (offsets[0] != offset) {
    for (size_type i = 1, e = offsets.size(); i < e; ++i) {
      cont[offsets[i - 1]] = std::move(cont[offsets[i]]);
    }
    cont[offset] = std::move(v);
    return offset;
  }
  for (size_type i = offsets.size() - 1; i > adjust; --i) {
    cont[offsets[i]] = std::move(cont[offsets[i - 1]]);
  }
  cont[offsets[adjust]] = std::move(v);
  return offsets[adjust];
}

// Erase one element and preserve heap order
template <typename size_type, class Container>
size_type erase(size_type offset, Container& cont) {
  size_type size = cont.size();
  if (offset + 1 == size) {
    auto ret = next(offset + 1, size);
    cont.resize(size - 1);
    return ret ? ret - 1 : size - 1;
  }
  std::vector<size_type> offsets;
  offsets.reserve(size);
  getOffsetRange(size, offsets, offset + 1);
  if (offsets[0] == offset) {
    for (size_type i = 1, e = offsets.size(); i < e; i++) {
      cont[offsets[i - 1]] = std::move(cont[offsets[i]]);
    }
  } else {
    for (size_type i = offsets.size() - 1; i > 0; --i) {
      cont[offsets[i]] = std::move(cont[offsets[i - 1]]);
    }
  }
  cont.resize(size - 1);
  return offset;
}

// Search lower bound in a container sorted in heap order.
// To speed up lower_bound for small containers, peel four iterations and use
// reverse compare to exit quickly.
// The branch inside the loop is converted to a cmov by the compiler. cmov are
// more efficient when the branch is unpredictable.
template <typename Compare, typename RCompare, typename Container>
typename Container::size_type lower_bound(
    Container& cont, Compare cmp, RCompare reverseCmp) {
  using size_type = typename Container::size_type;
  size_type size = cont.size();
  auto last = size;
  size_type offset = 0;
  if (size) {
    if (cmp(cont[offset])) {
      offset = 2 * offset + 2;
    } else {
      if (!reverseCmp(cont[offset])) {
        return offset;
      }
      last = offset;
      offset = 2 * offset + 1;
    }
    if (offset < size) {
      if (cmp(cont[offset])) {
        offset = 2 * offset + 2;
      } else {
        if (!reverseCmp(cont[offset])) {
          return offset;
        }
        last = offset;
        offset = 2 * offset + 1;
      }
      if (offset < size) {
        if (cmp(cont[offset])) {
          offset = 2 * offset + 2;
        } else {
          if (!reverseCmp(cont[offset])) {
            return offset;
          }
          last = offset;
          offset = 2 * offset + 1;
        }
        if (offset < size) {
          if (cmp(cont[offset])) {
            offset = 2 * offset + 2;
          } else {
            if (!reverseCmp(cont[offset])) {
              return offset;
            }
            last = offset;
            offset = 2 * offset + 1;
          }
          for (; offset < size; offset++) {
            if (cmp(cont[offset])) {
              offset = 2 * offset + 1;
            } else {
              last = offset;
              offset = 2 * offset;
            }
          }
        }
      }
    }
  }
  return last;
}

template <typename Compare, typename Container>
typename Container::size_type upper_bound(Container& cont, Compare cmp) {
  using size_type = typename Container::size_type;
  auto size = cont.size();
  auto last = size;
  for (size_type offset = 0; offset < size; offset++) {
    if (!cmp(cont[offset])) {
      offset = 2 * offset + 1;
    } else {
      last = offset;
      offset = 2 * offset;
    }
  }
  return last;
}

// Helper functions below are similar to sorted containers. Wherever
// applicable renamed to heap containers.
template <typename, typename Compare, typename Key, typename T>
struct heap_vector_enable_if_is_transparent {};

template <typename Compare, typename Key, typename T>
struct heap_vector_enable_if_is_transparent<
    void_t<typename Compare::is_transparent>,
    Compare,
    Key,
    T> {
  using type = T;
};

// This wrapper goes around a GrowthPolicy and provides iterator
// preservation semantics, but only if the growth policy is not the
// default (i.e. nothing).
template <class Policy>
struct growth_policy_wrapper : private Policy {
  template <class Container, class Iterator>
  Iterator increase_capacity(Container& c, Iterator desired_insertion) {
    using diff_t = typename Container::difference_type;
    diff_t d = desired_insertion - c.begin();
    Policy::increase_capacity(c);
    return c.begin() + d;
  }
};
template <>
struct growth_policy_wrapper<void> {
  template <class Container, class Iterator>
  Iterator increase_capacity(Container&, Iterator it) {
    return it;
  }
};

template <class OurContainer, class Container, class InputIterator>
void bulk_insert(
    OurContainer& sorted,
    Container& cont,
    InputIterator first,
    InputIterator last) {
  assert(first != last);

  auto const prev_size = cont.size();
  cont.insert(cont.end(), first, last);
  auto const middle = cont.begin() + prev_size;

  auto const& cmp(sorted.value_comp());
  if (!std::is_sorted(middle, cont.end(), cmp)) {
    std::sort(middle, cont.end(), cmp);
  }
  if (middle != cont.begin() && !cmp(*(middle - 1), *middle)) {
    std::inplace_merge(cont.begin(), middle, cont.end(), cmp);
  }
  cont.erase(
      std::unique(
          cont.begin(),
          cont.end(),
          [&](typename OurContainer::value_type const& a,
              typename OurContainer::value_type const& b) {
            return !cmp(a, b) && !cmp(b, a);
          }),
      cont.end());
  heapify(cont);
}

template <typename Container, typename Compare>
bool is_sorted_unique(Container const& container, Compare const& comp) {
  if (container.empty()) {
    return true;
  }
  auto const e = container.end();
  for (auto a = container.begin(), b = std::next(a); b != e; ++a, ++b) {
    if (!comp(*a, *b)) {
      return false;
    }
  }
  return true;
}

template <typename Container, typename Compare>
Container&& as_sorted_unique(Container&& container, Compare const& comp) {
  std::sort(container.begin(), container.end(), comp);
  container.erase(
      std::unique(
          container.begin(),
          container.end(),
          [&](auto const& a, auto const& b) {
            return !comp(a, b) && !comp(b, a);
          }),
      container.end());
  return static_cast<Container&&>(container);
}

// class value_compare_map is used to compare map elements.
template <class Compare>
struct value_compare_map : Compare {
  template <typename... value_type>
  auto operator()(const value_type&... a) const
      noexcept(is_nothrow_invocable_v<const Compare&, decltype((a.first))...>)
          -> invoke_result_t<const Compare&, decltype((a.first))...> {
    return Compare::operator()(a.first...);
  }

  template <typename value_type>
  const auto& getKey(const value_type& a) const noexcept {
    return a.first;
  }

  explicit value_compare_map(const Compare& c) noexcept(
      std::is_nothrow_copy_constructible<Compare>::value)
      : Compare(c) {}
};

// wrapper class value_compare_set for set elements.
template <class Compare>
struct value_compare_set : Compare {
  using Compare::operator();

  template <typename value_type>
  value_type& getKey(value_type& a) const noexcept {
    return a;
  }

  explicit value_compare_set(const Compare& c) noexcept(
      std::is_nothrow_copy_constructible<Compare>::value)
      : Compare(c) {}
};

/**
 * A heap_vector_container is a container similar to std::set<>, but
 * implemented as a heap array with std::vector<>.
 * This class contains shared implementation between set and map. It
 * fully implements set methods and used as base class for map.
 *
 * @tparam T               Data type to store
 * @tparam Compare         Comparison function that imposes a
 *                              strict weak ordering over instances of T
 * @tparam Allocator       allocation policy
 * @tparam GrowthPolicy    policy object to control growth
 * @tparam Container       underlying vector where elements are stored
 * @tparam KeyT            key type, for set it is same as T.
 * @tparam ValueCompare    wrapper class to compare Container::value_type
 *
 */
template <
    class T,
    class Compare = std::less<T>,
    class Allocator = std::allocator<T>,
    class GrowthPolicy = void,
    class Container = std::vector<T, Allocator>,
    class KeyT = T,
    class ValueCompare = value_compare_set<Compare>>
class heap_vector_container : growth_policy_wrapper<GrowthPolicy> {
 protected:
  growth_policy_wrapper<GrowthPolicy>& get_growth_policy() { return *this; }

  template <typename K, typename V, typename C = Compare>
  using if_is_transparent =
      _t<heap_vector_enable_if_is_transparent<void, C, K, V>>;

  struct EBO;

 public:
  using key_type = KeyT;
  using value_type = T;
  using key_compare = Compare;
  using value_compare = ValueCompare;
  using allocator_type = Allocator;
  using container_type = Container;
  using pointer = typename Container::pointer;
  using reference = typename Container::reference;
  using const_reference = typename Container::const_reference;
  using difference_type = typename Container::difference_type;
  using size_type = typename Container::size_type;

  // Defines inorder iterator for heap set.
  template <typename Iter>
  struct heap_iterator {
    using iterator_category = std::random_access_iterator_tag;
    using size_type = typename Container::size_type;
    using difference_type =
        typename std::iterator_traits<Iter>::difference_type;
    using value_type = typename std::iterator_traits<Iter>::value_type;
    using pointer = typename std::iterator_traits<Iter>::pointer;
    using reference = typename std::iterator_traits<Iter>::reference;

    heap_iterator() = default;
    template <typename C>
    heap_iterator(Iter ptr, C* cont) {
      ptr_ = ptr;
      cont_ = const_cast<Container*>(cont);
    }

    template <
        typename I2,
        typename = typename std::enable_if<
            std::is_same<typename Container::iterator, I2>::value>::type>
    /* implicit */ heap_iterator(const heap_iterator<I2>& rawIterator)
        : ptr_(rawIterator.ptr_), cont_(rawIterator.cont_) {}

    ~heap_iterator() = default;

    heap_iterator(const heap_iterator& rawIterator) = default;

    heap_iterator& operator=(const heap_iterator& rawIterator) = default;
    heap_iterator& operator=(Iter ptr) {
      assert(
          (ptr - cont_->begin()) >= 0 &&
          (ptr - cont_->begin()) <= (difference_type)cont_->size());
      ptr_ = ptr;
      return (*this);
    }

    bool operator==(const heap_iterator& rawIterator) const {
      return ptr_ == rawIterator.ptr_;
    }
    bool operator!=(const heap_iterator& rawIterator) const {
      return !operator==(rawIterator);
    }

    heap_iterator& operator+=(const difference_type& movement) {
      size_type offset = ptr_ - cont_->begin() + 1;
      auto size = cont_->size();
      if (movement < 0) {
        difference_type i = 0;

        if (offset - 1 == size) {
          // handle --end()
          offset = heap_vector_detail::lastOffset(size) + 1;
          i = -1;
        }
        for (; i > movement; i--) {
          offset = heap_vector_detail::prev(offset, size);
        }
      } else {
        for (difference_type i = 0; i < movement; i++) {
          offset = heap_vector_detail::next(offset, size);
        }
      }
      ptr_ = cont_->begin() + (offset == 0 ? cont_->size() : offset - 1);
      return (*this);
    }

    heap_iterator& operator-=(const difference_type& movement) {
      return operator+=(-movement);
    }
    heap_iterator& operator++() { return operator+=(1); }
    heap_iterator& operator--() { return operator-=(1); }
    heap_iterator operator++(int) {
      auto temp(*this);
      operator+=(1);
      return temp;
    }
    heap_iterator operator--(int) {
      auto temp(*this);
      operator-=(1);
      return temp;
    }
    heap_iterator operator+(const difference_type& movement) {
      auto temp(*this);
      temp += movement;
      return temp;
    }
    heap_iterator operator+(const difference_type& movement) const {
      auto temp(*this);
      temp += movement;
      return temp;
    }

    heap_iterator operator-(const difference_type& movement) {
      auto temp(*this);
      temp -= movement;
      return temp;
    }

    heap_iterator operator-(const difference_type& movement) const {
      auto temp(*this);
      temp -= movement;
      return temp;
    }

    difference_type operator-(const heap_iterator& rawIterator) {
      assert(cont_ == rawIterator.cont_);
      size_type offset0 = ptr_ - cont_->begin();
      size_type offset1 = rawIterator.ptr_ - cont_->begin();
      if (offset1 == offset0)
        return 0;
      return heap_vector_detail::distance(*cont_, offset1, offset0);
    }

    difference_type operator-(const heap_iterator& rawIterator) const {
      assert(cont_ == rawIterator.cont_);
      size_type offset0 = ptr_ - cont_->begin();
      size_type offset1 = rawIterator.ptr_ - cont_->begin();
      if (offset1 == offset0)
        return 0;
      return heap_vector_detail::distance(*cont_, offset1, offset0);
    }

    reference operator*() const { return *ptr_; }
    pointer operator->() const {
      if constexpr (std::is_pointer_v<Iter>) {
        return ptr_;
      } else {
        return ptr_.operator->();
      }
    }

   protected:
    template <typename I2>
    friend struct heap_iterator;

    template <
        typename T2,
        typename Compare2,
        typename Allocator2,
        typename GrowthPolicy2,
        typename Container2,
        typename KeyT2,
        typename ValueCompare2>
    friend class heap_vector_container;

    template <
        typename Key2,
        typename Value2,
        typename Compare2,
        typename Allocator2,
        typename GrowthPolicy2,
        typename Container2>
    friend class ::folly::heap_vector_map;

    Iter ptr_;
    Container* cont_;
  };

  using iterator = heap_iterator<typename Container::iterator>;
  using const_iterator = heap_iterator<typename Container::const_iterator>;
  using reverse_iterator = std::reverse_iterator<iterator>;
  using const_reverse_iterator = std::reverse_iterator<const_iterator>;

  heap_vector_container() : m_(value_compare(Compare()), Allocator()) {}

  heap_vector_container(const heap_vector_container&) = default;

  heap_vector_container(
      const heap_vector_container& other, const Allocator& alloc)
      : m_(other.m_, alloc) {}

  heap_vector_container(heap_vector_container&&) = default;

  heap_vector_container(
      heap_vector_container&& other,
      const Allocator& alloc) noexcept(std::
                                           is_nothrow_constructible<
                                               EBO,
                                               EBO&&,
                                               const Allocator&>::value)
      : m_(std::move(other.m_), alloc) {}

  explicit heap_vector_container(const Allocator& alloc)
      : m_(value_compare(Compare()), alloc) {}

  explicit heap_vector_container(
      const Compare& comp, const Allocator& alloc = Allocator())
      : m_(value_compare(comp), alloc) {}

  template <class InputIterator>
  explicit heap_vector_container(
      InputIterator first,
      InputIterator last,
      const Compare& comp = Compare(),
      const Allocator& alloc = Allocator())
      : m_(value_compare(comp), alloc) {
    insert(first, last);
  }

  template <class InputIterator>
  heap_vector_container(
      InputIterator first, InputIterator last, const Allocator& alloc)
      : m_(value_compare(Compare()), alloc) {
    insert(first, last);
  }

  /* implicit */ heap_vector_container(
      std::initializer_list<value_type> list,
      const Compare& comp = Compare(),
      const Allocator& alloc = Allocator())
      : m_(value_compare(comp), alloc) {
    insert(list.begin(), list.end());
  }

  heap_vector_container(
      std::initializer_list<value_type> list, const Allocator& alloc)
      : m_(value_compare(Compare()), alloc) {
    insert(list.begin(), list.end());
  }

  // Construct a heap_vector_container by stealing the storage of a prefilled
  // container. The container need not be sorted already. This supports
  // bulk construction of heap_vector_container with zero allocations, not
  // counting those performed by the caller.
  // Note that `heap_vector_container(const Container& container)` is not
  // provided, since the purpose of this constructor is to avoid an unnecessary
  // copy.
  explicit heap_vector_container(
      Container&& container,
      const Compare& comp = Compare()) noexcept(std::
                                                    is_nothrow_constructible<
                                                        EBO,
                                                        value_compare,
                                                        Container&&>::value)
      : heap_vector_container(
            sorted_unique,
            heap_vector_detail::as_sorted_unique(
                std::move(container), value_compare(comp)),
            comp) {}

  // Construct a heap_vector_container by stealing the storage of a prefilled
  // container. Its elements must be sorted and unique, as sorted_unique_t
  // hints. Supports bulk construction of heap_vector_container with zero
  // allocations, not counting those performed by the caller.
  // Note that `heap_vector_container(sorted_unique_t, const Container&
  // container)` is not provided, since the purpose of this constructor is to
  // avoid an extra copy.
  heap_vector_container(
      sorted_unique_t /* unused */,
      Container&& container,
      const Compare& comp = Compare()) noexcept(std::
                                                    is_nothrow_constructible<
                                                        EBO,
                                                        value_compare,
                                                        Container&&>::value)
      : m_(value_compare(comp), std::move(container)) {
    assert(heap_vector_detail::is_sorted_unique(m_.cont_, value_comp()));
    heap_vector_detail::heapify(m_.cont_);
  }

  Allocator get_allocator() const { return m_.cont_.get_allocator(); }

  const Container& get_container() const noexcept { return m_.cont_; }

  heap_vector_container& operator=(const heap_vector_container& other) =
      default;

  heap_vector_container& operator=(heap_vector_container&& other) = default;

  heap_vector_container& operator=(std::initializer_list<value_type> ilist) {
    clear();
    insert(ilist.begin(), ilist.end());
    return *this;
  }

  key_compare key_comp() const { return m_; }
  value_compare value_comp() const { return m_; }

  iterator begin() {
    if (size()) {
      return iterator(
          m_.cont_.begin() + heap_vector_detail::firstOffset(size()),
          &m_.cont_);
    }
    return iterator(m_.cont_.begin(), &m_.cont_);
  }
  iterator end() { return iterator(m_.cont_.end(), &m_.cont_); }
  const_iterator cbegin() const {
    if (size()) {
      return const_iterator(
          m_.cont_.cbegin() + heap_vector_detail::firstOffset(size()),
          &m_.cont_);
    }
    return const_iterator(m_.cont_.cbegin(), &m_.cont_);
  }
  const_iterator begin() const {
    if (size()) {
      return const_iterator(
          m_.cont_.cbegin() + heap_vector_detail::firstOffset(size()),
          &m_.cont_);
    }
    return const_iterator(m_.cont_.begin(), &m_.cont_);
  }
  const_iterator cend() const {
    return const_iterator(m_.cont_.cend(), &m_.cont_);
  }
  const_iterator end() const {
    return const_iterator(m_.cont_.end(), &m_.cont_);
  }
  reverse_iterator rbegin() { return reverse_iterator(end()); }
  reverse_iterator rend() { return reverse_iterator(begin()); }
  const_reverse_iterator rbegin() const {
    return const_reverse_iterator(end());
  }
  const_reverse_iterator rend() const {
    return const_reverse_iterator(begin());
  }
  const_reverse_iterator crbegin() const {
    return const_reverse_iterator(end());
  }
  const_reverse_iterator crend() const {
    return const_reverse_iterator(begin());
  }

  void clear() { return m_.cont_.clear(); }
  size_type size() const { return m_.cont_.size(); }
  size_type max_size() const { return m_.cont_.max_size(); }
  bool empty() const { return m_.cont_.empty(); }
  void reserve(size_type s) { return m_.cont_.reserve(s); }
  void shrink_to_fit() { m_.cont_.shrink_to_fit(); }
  size_type capacity() const { return m_.cont_.capacity(); }
  const value_type* data() const noexcept { return m_.cont_.data(); }

  std::pair<iterator, bool> insert(const value_type& value) {
    iterator it = lower_bound(m_.getKey(value));
    if (it == end() || value_comp()(value, *it)) {
      auto offset = it.ptr_ - m_.cont_.begin();
      get_growth_policy().increase_capacity(*this, it);
      m_.cont_.push_back(value);
      offset = heap_vector_detail::insert(offset, m_.cont_);
      it = m_.cont_.begin() + offset;
      return std::make_pair(it, true);
    }
    return std::make_pair(it, false);
  }

  std::pair<iterator, bool> insert(value_type&& value) {
    iterator it = lower_bound(m_.getKey(value));
    if (it == end() || value_comp()(value, *it)) {
      auto offset = it.ptr_ - m_.cont_.begin();
      get_growth_policy().increase_capacity(*this, it);
      m_.cont_.push_back(std::move(value));
      offset = heap_vector_detail::insert(offset, m_.cont_);
      it = m_.cont_.begin() + offset;
      return std::make_pair(it, true);
    }
    return std::make_pair(it, false);
  }
  /* There is no benefit of using hint. Keep it for compatibility
   * Ignore and insert */
  iterator insert(const_iterator /* hint */, const value_type& value) {
    return insert(value).first;
  }

  iterator insert(const_iterator /* hint */, value_type&& value) {
    return insert(std::move(value)).first;
  }

  template <class InputIterator>
  void insert(InputIterator first, InputIterator last) {
    if (first == last) {
      return;
    }
    if (iterator_has_known_distance_v<InputIterator, InputIterator> &&
        std::distance(first, last) == 1) {
      insert(*first);
      return;
    }
    std::sort(m_.cont_.begin(), m_.cont_.end(), value_comp());
    heap_vector_detail::bulk_insert(*this, m_.cont_, first, last);
  }

  void insert(std::initializer_list<value_type> ilist) {
    insert(ilist.begin(), ilist.end());
  }
  // emplace isn't better than insert for heap_vector_container, but aids
  // compatibility
  template <typename... Args>
  std::pair<iterator, bool> emplace(Args&&... args) {
    std::aligned_storage_t<sizeof(value_type), alignof(value_type)> b;
    auto* p = static_cast<value_type*>(static_cast<void*>(&b));
    auto a = get_allocator();
    std::allocator_traits<allocator_type>::construct(
        a, p, std::forward<Args>(args)...);
    auto g = makeGuard(
        [&]() { std::allocator_traits<allocator_type>::destroy(a, p); });
    return insert(std::move(*p));
  }

  std::pair<iterator, bool> emplace(const value_type& value) {
    return insert(value);
  }

  std::pair<iterator, bool> emplace(value_type&& value) {
    return insert(std::move(value));
  }

  // emplace_hint isn't better than insert for heap_vector_container, but aids
  // compatibility
  template <typename... Args>
  iterator emplace_hint(const_iterator /* hint */, Args&&... args) {
    return emplace(std::forward<Args>(args)...).first;
  }

  iterator emplace_hint(const_iterator hint, const value_type& value) {
    return insert(hint, value);
  }

  iterator emplace_hint(const_iterator hint, value_type&& value) {
    return insert(hint, std::move(value));
  }

  size_type erase(const key_type& key) {
    iterator it = find(key);
    if (it == end()) {
      return 0;
    }
    heap_vector_detail::erase(it.ptr_ - m_.cont_.begin(), m_.cont_);
    return 1;
  }

  iterator erase(const_iterator it) {
    auto offset =
        heap_vector_detail::erase(it.ptr_ - m_.cont_.begin(), m_.cont_);
    iterator ret = end();
    ret = m_.cont_.begin() + offset;
    return ret;
  }

  iterator erase(const_iterator first, const_iterator last) {
    if (first == last) {
      return end();
    }
    auto dist = last - first;
    if (dist <= 0) {
      return end();
    }
    if (dist == 1) {
      return erase(first);
    }
    auto it = m_.cont_.begin() + (first - begin());
    std::sort(m_.cont_.begin(), m_.cont_.end(), value_comp());
    it = m_.cont_.erase(it, it + dist);
    heap_vector_detail::heapify(m_.cont_);
    return begin() + (it - m_.cont_.begin());
  }

  iterator find(const key_type& key) { return find_(*this, key); }

  const_iterator find(const key_type& key) const { return find_(*this, key); }

  template <typename K>
  if_is_transparent<K, iterator> find(const K& key) {
    return find_(*this, key);
  }

  template <typename K>
  if_is_transparent<K, const_iterator> find(const K& key) const {
    return find_(*this, key);
  }

  size_type count(const key_type& key) const {
    return find(key) == end() ? 0 : 1;
  }

  template <typename K>
  if_is_transparent<K, size_type> count(const K& key) const {
    return find(key) == end() ? 0 : 1;
  }

  bool contains(const key_type& key) const { return find(key) != end(); }

  template <typename K>
  if_is_transparent<K, bool> contains(const K& key) const {
    return find(key) != end();
  }

  iterator lower_bound(const key_type& key) { return lower_bound(*this, key); }

  const_iterator lower_bound(const key_type& key) const {
    return lower_bound(*this, key);
  }

  template <typename K>
  if_is_transparent<K, iterator> lower_bound(const K& key) {
    return lower_bound(*this, key);
  }

  template <typename K>
  if_is_transparent<K, const_iterator> lower_bound(const K& key) const {
    return lower_bound(*this, key);
  }

  iterator upper_bound(const key_type& key) { return upper_bound(*this, key); }

  const_iterator upper_bound(const key_type& key) const {
    return upper_bound(*this, key);
  }

  template <typename K>
  if_is_transparent<K, iterator> upper_bound(const K& key) {
    return upper_bound(*this, key);
  }

  template <typename K>
  if_is_transparent<K, const_iterator> upper_bound(const K& key) const {
    return upper_bound(*this, key);
  }

  std::pair<iterator, iterator> equal_range(const key_type& key) {
    return {lower_bound(key), upper_bound(key)};
  }

  std::pair<const_iterator, const_iterator> equal_range(
      const key_type& key) const {
    return {lower_bound(key), upper_bound(key)};
  }

  template <typename K>
  if_is_transparent<K, std::pair<iterator, iterator>> equal_range(
      const K& key) {
    return {lower_bound(key), upper_bound(key)};
  }

  template <typename K>
  if_is_transparent<K, std::pair<const_iterator, const_iterator>> equal_range(
      const K& key) const {
    return {lower_bound(key), upper_bound(key)};
  }

  void swap(heap_vector_container& o) noexcept(
      std::is_nothrow_swappable<Compare>::value &&
      noexcept(std::declval<Container&>().swap(std::declval<Container&>()))) {
    using std::swap; // Allow ADL for swap(); fall back to std::swap().
    Compare& a = m_;
    Compare& b = o.m_;
    swap(a, b);
    m_.cont_.swap(o.m_.cont_);
  }

  bool operator==(const heap_vector_container& other) const {
    return m_.cont_ == other.m_.cont_;
  }

  bool operator!=(const heap_vector_container& other) const {
    return !operator==(other);
  }

  bool operator<(const heap_vector_container& other) const {
    return std::lexicographical_compare(
        begin(), end(), other.begin(), other.end(), value_comp());
  }
  bool operator>(const heap_vector_container& other) const {
    return other < *this;
  }
  bool operator<=(const heap_vector_container& other) const {
    return !operator>(other);
  }
  bool operator>=(const heap_vector_container& other) const {
    return !operator<(other);
  }

  // Use underlying vector iterators to quickly traverse heap container.
  // Note elements are traversed following the heap order, i.e., memory
  // storage order.
  Range<typename Container::iterator> iterate() noexcept {
    return Range<typename Container::iterator>(
        m_.cont_.begin(), m_.cont_.end());
  }

  const Range<typename Container::const_iterator> iterate() const noexcept {
    return Range<typename Container::const_iterator>(
        m_.cont_.begin(), m_.cont_.end());
  }

 protected:
  // This is to get the empty base optimization
  struct EBO : value_compare {
    explicit EBO(const value_compare& c, const Allocator& alloc) noexcept(
        std::is_nothrow_default_constructible<Container>::value)
        : value_compare(c), cont_(alloc) {}
    EBO(const EBO& other, const Allocator& alloc) noexcept(
        std::is_nothrow_constructible<
            Container,
            const Container&,
            const Allocator&>::value)
        : value_compare(static_cast<const value_compare&>(other)),
          cont_(other.cont_, alloc) {}
    EBO(EBO&& other, const Allocator& alloc) noexcept(
        std::is_nothrow_constructible<
            Container,
            Container&&,
            const Allocator&>::value)
        : value_compare(static_cast<value_compare&&>(other)),
          cont_(std::move(other.cont_), alloc) {}
    EBO(const Compare& c, Container&& cont) noexcept(
        std::is_nothrow_move_constructible<Container>::value)
        : value_compare(c), cont_(std::move(cont)) {}
    Container cont_;
  } m_;

  template <typename Self>
  using self_iterator_t = typename std::
      conditional<std::is_const<Self>::value, const_iterator, iterator>::type;

  template <typename Self, typename K>
  static self_iterator_t<Self> find_(Self& self, K const& key) {
    self_iterator_t<Self> end = self.end();
    self_iterator_t<Self> it = self.lower_bound(key);
    if (it == end || !self.key_comp()(key, self.m_.getKey(*it))) {
      return it;
    }
    return end;
  }

  template <typename Self, typename K>
  static self_iterator_t<Self> lower_bound(Self& self, K const& key) {
    auto c = self.key_comp();
    auto cmp = [&](auto const& a) { return c(self.m_.getKey(a), key); };
    auto reverseCmp = [&](auto const& a) { return c(key, self.m_.getKey(a)); };
    auto offset =
        heap_vector_detail::lower_bound(self.m_.cont_, cmp, reverseCmp);
    self_iterator_t<Self> ret = self.end();
    ret = self.m_.cont_.begin() + offset;
    return ret;
  }

  template <typename Self, typename K>
  static self_iterator_t<Self> upper_bound(Self& self, K const& key) {
    auto c = self.key_comp();
    auto cmp = [&](auto const& a) { return c(key, self.m_.getKey(a)); };
    auto offset = heap_vector_detail::upper_bound(self.m_.cont_, cmp);
    self_iterator_t<Self> ret = self.end();
    ret = self.m_.cont_.begin() + offset;
    return ret;
  }
};

} // namespace heap_vector_detail

} // namespace detail

/* heap_vector_set is a specialization of heap_vector_container
 *
 * @tparam T               Data type to store
 * @tparam Compare         Comparison function that imposes a
 *                              strict weak ordering over instances of T
 * @tparam Allocator       allocation policy
 * @tparam GrowthPolicy    policy object to control growth
 * @tparam Container       underlying vector where elements are stored
 */
template <
    class T,
    class Compare = std::less<T>,
    class Allocator = std::allocator<T>,
    class GrowthPolicy = void,
    class Container = std::vector<T, Allocator>>
class heap_vector_set
    : public detail::heap_vector_detail::heap_vector_container<
          T,
          Compare,
          Allocator,
          GrowthPolicy,
          Container,
          T,
          detail::heap_vector_detail::value_compare_set<Compare>> {
 private:
  using heap_vector_container =
      detail::heap_vector_detail::heap_vector_container<
          T,
          Compare,
          Allocator,
          GrowthPolicy,
          Container,
          T,
          detail::heap_vector_detail::value_compare_set<Compare>>;

 public:
  using heap_vector_container::heap_vector_container;
};

// Swap function that can be found using ADL.
template <class T, class C, class A, class G>
inline void swap(
    heap_vector_set<T, C, A, G>& a, heap_vector_set<T, C, A, G>& b) noexcept {
  return a.swap(b);
}

#if FOLLY_HAS_MEMORY_RESOURCE

namespace pmr {

template <
    class T,
    class Compare = std::less<T>,
    class GrowthPolicy = void,
    class Container =
        std::vector<T, folly::detail::std_pmr::polymorphic_allocator<T>>>
using heap_vector_set = folly::heap_vector_set<
    T,
    Compare,
    folly::detail::std_pmr::polymorphic_allocator<T>,
    GrowthPolicy,
    Container>;

} // namespace pmr

#endif

//////////////////////////////////////////////////////////////////////

/**
 * A heap_vector_map based on heap layout.
 *
 * @tparam Key           Key type
 * @tparam Value         Value type
 * @tparam Compare       Function that can compare key types and impose
 *                            a strict weak ordering over them.
 * @tparam Allocator     allocation policy
 * @tparam GrowthPolicy  policy object to control growth
 *
 */

template <
    class Key,
    class Value,
    class Compare = std::less<Key>,
    class Allocator = std::allocator<std::pair<Key, Value>>,
    class GrowthPolicy = void,
    class Container = std::vector<std::pair<Key, Value>, Allocator>>
class heap_vector_map
    : public detail::heap_vector_detail::heap_vector_container<
          typename Container::value_type,
          Compare,
          Allocator,
          GrowthPolicy,
          Container,
          Key,
          detail::heap_vector_detail::value_compare_map<Compare>> {
 public:
  using key_type = Key;
  using mapped_type = Value;
  using value_type = typename Container::value_type;
  using key_compare = Compare;
  using allocator_type = Allocator;
  using container_type = Container;
  using pointer = typename Container::pointer;
  using reference = typename Container::reference;
  using const_reference = typename Container::const_reference;
  using difference_type = typename Container::difference_type;
  using size_type = typename Container::size_type;
  using value_compare = detail::heap_vector_detail::value_compare_map<Compare>;

 protected:
  using heap_vector_container =
      detail::heap_vector_detail::heap_vector_container<
          value_type,
          key_compare,
          Allocator,
          GrowthPolicy,
          Container,
          key_type,
          value_compare>;
  using heap_vector_container::get_growth_policy;
  using heap_vector_container::m_;

 public:
  using iterator = typename heap_vector_container::iterator;
  using const_iterator = typename heap_vector_container::const_iterator;
  using reverse_iterator = std::reverse_iterator<iterator>;
  using const_reverse_iterator = std::reverse_iterator<const_iterator>;

  // Since heap_vector_container methods are publicly available through
  // inheritance, just expose method used within this class.
  using heap_vector_container::end;
  using heap_vector_container::find;
  using heap_vector_container::heap_vector_container;
  using heap_vector_container::key_comp;
  using heap_vector_container::lower_bound;

  mapped_type& at(const key_type& key) {
    iterator it = find(key);
    if (it != end()) {
      return it->second;
    }
    throw_exception<std::out_of_range>("heap_vector_map::at");
  }

  const mapped_type& at(const key_type& key) const {
    const_iterator it = find(key);
    if (it != end()) {
      return it->second;
    }
    throw_exception<std::out_of_range>("heap_vector_map::at");
  }

  mapped_type& operator[](const key_type& key) {
    iterator it = lower_bound(key);
    if (it == end() || key_comp()(key, it->first)) {
      auto offset = it.ptr_ - m_.cont_.begin();
      get_growth_policy().increase_capacity(*this, it);
      m_.cont_.emplace_back(key, mapped_type());
      offset = detail::heap_vector_detail::insert(offset, m_.cont_);
      it = m_.cont_.begin() + offset;
      return it->second;
    }
    return it->second;
  }
};

// Swap function that can be found using ADL.
template <class K, class V, class C, class A, class G>
inline void swap(
    heap_vector_map<K, V, C, A, G>& a,
    heap_vector_map<K, V, C, A, G>& b) noexcept {
  return a.swap(b);
}

#if FOLLY_HAS_MEMORY_RESOURCE

namespace pmr {

template <
    class Key,
    class Value,
    class Compare = std::less<Key>,
    class GrowthPolicy = void,
    class Container = std::vector<
        std::pair<Key, Value>,
        folly::detail::std_pmr::polymorphic_allocator<std::pair<Key, Value>>>>
using heap_vector_map = folly::heap_vector_map<
    Key,
    Value,
    Compare,
    folly::detail::std_pmr::polymorphic_allocator<std::pair<Key, Value>>,
    GrowthPolicy,
    Container>;

} // namespace pmr

#endif

// Specialize heap_vector_map to integral key type and std::less comparaison.
// small_heap_map achieve a very fast find for small map < 200 elements.
template <
    typename Key,
    typename Value,
    typename SizeType = uint32_t,
    class Container = folly::small_vector<
        std::pair<Key, Value>,
        0,
        folly::small_vector_policy::policy_size_type<SizeType>>,
    typename = std::enable_if_t<
        std::is_integral<Key>::value || std::is_enum<Key>::value>>
class small_heap_vector_map : public folly::heap_vector_map<
                                  Key,
                                  Value,
                                  std::less<Key>,
                                  typename Container::allocator_type,
                                  void,
                                  Container> {
 public:
  using key_type = Key;
  using mapped_type = Value;
  using value_type = typename Container::value_type;
  using key_compare = std::less<Key>;
  using allocator_type = typename Container::allocator_type;
  using container_type = Container;
  using pointer = typename Container::pointer;
  using reference = typename Container::reference;
  using const_reference = typename Container::const_reference;
  using difference_type = typename Container::difference_type;
  using size_type = typename Container::size_type;
  using value_compare =
      detail::heap_vector_detail::value_compare_map<std::less<Key>>;

 private:
  using heap_vector_map = folly::
      heap_vector_map<Key, Value, key_compare, allocator_type, void, Container>;

  using heap_vector_map::m_;

 public:
  using iterator = typename heap_vector_map::iterator;
  using const_iterator = typename heap_vector_map::const_iterator;
  using reverse_iterator = std::reverse_iterator<iterator>;
  using const_reverse_iterator = std::reverse_iterator<const_iterator>;

  using heap_vector_map::begin;
  using heap_vector_map::heap_vector_map;
  using heap_vector_map::size;
  iterator find(const key_type key) {
    auto offset = find_(*this, key);
    iterator ret = begin();
    ret = m_.cont_.begin() + offset;
    return ret;
  }

  const_iterator find(const key_type key) const {
    auto offset = find_(*this, key);
    const_iterator ret = begin();
    ret = m_.cont_.begin() + offset;
    return ret;
  }

 private:
  template <typename Self, typename K>
  static inline size_type find_(Self& self, K const key) {
    auto size = self.size();
    if (!size) {
      return 0;
    }
    auto& cont = self.m_.cont_;
    size_type offset = 1;
    auto cur_k = self.m_.getKey(cont[0]);
    for (int i = 0; i < 6; i++) {
      auto o = offset;
      offset = 2 * offset;
      if (cur_k <= key) {
        ++offset;
        if (cur_k == key) {
          return o - 1;
        }
      }
      if (offset > size) {
        return size;
      }
      cur_k = self.m_.getKey(cont[offset - 1]);
    }
    while (true) {
      auto lt = cur_k < key;
      if (cur_k == key) {
        return offset - 1;
      }
      offset = 2 * offset + lt;
      if (offset > size)
        return size;
      cur_k = self.m_.getKey(cont[offset - 1]);
    }
    return size;
  }
};

} // namespace folly