sapling/eden/fs/utils/PathMap.h
Adam Simpkins 05288c2b98 put PathFuncs.h in their own library rule
Summary:
This updates the eden/fs/utils/TARGETS file so that PathFuncs.h and
PathFuncs.cpp are in their own separate library.  The existing "utils" library
depends on it, but other users can now depend on PathFuncs.h without pulling in
the other code in utils.

I plan to make some of the privhelper code depend on PathFuncs.h in an upcoming
diff, and this makes it so that privhelper will not need to pull in the rest of
the utils code.

Reviewed By: bolinfest

Differential Revision: D5501253

fbshipit-source-id: 804067785ed663e223977d1c84ebcfc28b3f1642
2017-07-27 13:42:45 -07:00

299 lines
10 KiB
C++

/*
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#pragma once
#include <folly/FBVector.h>
#include <algorithm>
#include <functional>
#include <iterator>
#include <utility>
#include "eden/fs/utils/PathFuncs.h"
namespace facebook {
namespace eden {
/** An associative container that maps from one of our path types to an
* arbitrary value type.
*
* This is similar to std::map but has a couple of different properties:
* - lookups can be made using the Piece (non-stored) variant of the key
* type and won't require allocation just for the lookup.
* - The storage is a vector maintained in sorted order using a binary
* search (std::lower_bound). Out-of-order inserts require moving
* the guts of the vector around to make space and are therefore slower
* than the equivalent std::map. If bulk insert performance is critical,
* it is better to pre-sort the data to be inserted.
* - Since insert and erase operations move the vector contents around,
* those operations invalidate iterators.
*/
template <
typename Value,
typename Key = PathComponent,
typename Allocator = std::allocator<std::pair<Key, Value>>>
class PathMap : private folly::fbvector<std::pair<Key, Value>, Allocator> {
using Pair = std::pair<Key, Value>;
using Vector = folly::fbvector<Pair, Allocator>;
using Piece = typename Key::piece_type;
// Comparator that knows how compare Stored and Piece in the vector.
struct Compare {
// Compare two values that are convertible to the Piece type.
template <typename A, typename B>
typename std::enable_if<
std::is_convertible<A, Piece>::value &&
std::is_convertible<B, Piece>::value,
bool>::type
operator()(const A& a, const B& rhs) const {
return Piece(a) < Piece(rhs);
}
// Compare a Piece-convertible value against the stored Pair.
template <typename A, typename B, typename C>
typename std::enable_if<
std::is_convertible<A, Piece>::value &&
std::is_convertible<B, Piece>::value,
bool>::type
operator()(const A& a, const std::pair<B, C>& rhs) const {
return Piece(a) < Piece(rhs.first);
}
// Compare the stored Pair against a Piece-convertible value.
template <typename A, typename B, typename C>
typename std::enable_if<
std::is_convertible<A, Piece>::value &&
std::is_convertible<B, Piece>::value,
bool>::type
operator()(const std::pair<B, C>& lhs, const A& a) const {
return Piece(lhs.first) < Piece(a);
}
};
// Hold an instance of the comparator. It doesn't actually
// occupy any space.
Compare compare_;
public:
// Various type aliases to satisfy container concepts.
using key_type = Key;
using mapped_type = Value;
using value_type = typename Vector::value_type;
using key_compare = Compare;
using allocator_type = Allocator;
using reference = typename Allocator::reference;
using const_reference = typename Allocator::const_reference;
using iterator = typename Vector::iterator;
using const_iterator = typename Vector::const_iterator;
using size_type = typename Vector::size_type;
using difference_type = typename Vector::difference_type;
using pointer = typename Allocator::pointer;
using const_pointer = typename Allocator::const_pointer;
using reverse_iterator = typename Vector::reverse_iterator;
using const_reverse_iterator = typename Vector::const_reverse_iterator;
// Construct empty.
PathMap() {}
// Populate from an initializer_list.
PathMap(std::initializer_list<value_type> init)
: PathMap(init.begin(), init.end()) {}
// Populate from a pair of input iterators.
template <typename InputIterator>
PathMap(InputIterator first, InputIterator last) {
// The std::distance call is O(1) if the iterators are random-access, but
// O(n) otherwise. We're fine with the O(n) on the basis that if n is large
// enough to matter, the cost of iterating will be dwarfed by the cost
// of growing the storage several times during population.
this->reserve(std::distance(first, last));
for (; first != last; ++first) {
insert(*first);
}
}
// Inherit the underlying vector copy/assignment.
PathMap(const PathMap& other) : Vector(other) {}
PathMap& operator=(const PathMap& other) {
PathMap(other).swap(*this);
return *this;
}
// inherit Move construction.
PathMap(PathMap&& other) noexcept : Vector(std::move(other)) {}
PathMap& operator=(PathMap&& other) {
other.swap(*this);
return *this;
}
// inherit these methods from the underlying vector.
using Vector::begin;
using Vector::end;
using Vector::cbegin;
using Vector::cend;
using Vector::crbegin;
using Vector::crend;
using Vector::rbegin;
using Vector::rend;
using Vector::empty;
using Vector::size;
using Vector::max_size;
using Vector::clear;
using Vector::erase;
// Swap contents with another map.
void swap(PathMap& other) noexcept {
Vector::swap(other);
}
// lower_bound performs the binary search for locating keys.
iterator lower_bound(Piece key) {
return std::lower_bound(begin(), end(), key, compare_);
}
const_iterator lower_bound(Piece key) const {
return std::lower_bound(begin(), end(), key, compare_);
}
/** Find using the Piece representation of a key.
* Does not allocate a copy of the key string.
*/
iterator find(Piece key) {
auto iter = lower_bound(key);
if (iter != end() && compare_(key, iter->first)) {
// We found the right slot, but it is occupied by a different key.
return end();
}
return iter;
}
/** Find using the Piece representation of a key.
* Does not allocate a copy of the key string.
*/
const_iterator find(Piece key) const {
const auto iter = lower_bound(key);
if (iter != end() && compare_(key, iter->first)) {
// We found the right slot, but it is occupied by a different key.
return end();
}
return iter;
}
/** Insert a new key-value pair.
* If the key already exists, it is left unaltered.
* Returns a pair consisting of an iterator to the position for key and
* a boolean that is true if an insert took place. */
std::pair<iterator, bool> insert(const value_type& val) {
auto iter = lower_bound(val.first);
if (iter == end() || compare_(val.first, iter->first)) {
return std::make_pair(Vector::insert(iter, val), true);
}
return std::make_pair(iter, false);
}
/** Emplace a new key-value pair by constructing it in-place.
* If the key already exists, it is left unaltered.
* If an insertion happens, the args are forwarded to the Value
* constructor.
* Returns a pair consisting of an iterator to the position for key and
* a boolean that is true if an insert took place. */
template <typename... Args>
std::pair<iterator, bool> emplace(Piece key, Args&&... args) {
auto iter = lower_bound(key);
if (iter == end() || compare_(key, iter->first)) {
iter = Vector::emplace(
iter, std::make_pair(Key(key), Value(std::forward<Args>(args)...)));
return std::make_pair(iter, true);
}
return std::make_pair(iter, false);
}
/** Returns a reference to the map position for key, creating it needed.
* If the key is already present, no additional allocations are performed. */
mapped_type& operator[](Piece key) {
auto iter = lower_bound(key);
if (iter == end() || compare_(key, iter->first)) {
// Not yet present, make a new one
iter = Vector::insert(iter, std::make_pair(Key(key), mapped_type()));
}
return iter->second;
}
/** Returns a reference to the map position for key, if present.
* Throws std::out_of_range if the key is not present (this const
* form is not allowed to mutate the map). */
const mapped_type& operator[](Piece key) const {
return at(key);
}
/** Returns a reference to the map position for key, if present.
* Throws std::out_of_range if the key is not present. */
mapped_type& at(Piece key) {
auto iter = find(key);
if (iter == end()) {
throw std::out_of_range(folly::to<std::string>("no such key ", key));
}
return iter->second;
}
/** Returns a reference to the map position for key, if present.
* Throws std::out_of_range if the key is not present. */
const mapped_type& at(Piece key) const {
const auto iter = find(key);
if (iter == end()) {
throw std::out_of_range(folly::to<std::string>("no such key ", key));
}
return iter->second;
}
/** Erase the value associated with key.
* Does not allocate any additional memory to look up the key.
* Returns the number of matching elements that were erased; this is
* always either 1 or 0. */
size_type erase(Piece key) {
auto iter = find(key);
if (iter == end()) {
return 0;
}
erase(iter);
return 1;
}
/// Equality operator.
template <typename V, typename K, typename A>
friend bool operator==(
const PathMap<V, K, A>& lhs,
const PathMap<V, K, A>& rhs);
/// Inequality operator.
template <typename V, typename K, typename A>
friend bool operator!=(
const PathMap<V, K, A>& lhs,
const PathMap<V, K, A>& rhs);
};
// Implementations of the equality operators; gcc hates us if we
// define them inline in the class above.
/// Equality operator.
template <typename V, typename K, typename A>
bool operator==(const PathMap<V, K, A>& lhs, const PathMap<V, K, A>& rhs) {
// reinterpret lhs as the underlying vector type.
const folly::fbvector<std::pair<K, V>, A>& vector = lhs;
return vector == rhs;
}
/// Inequality operator.
template <typename V, typename K, typename A>
bool operator!=(const PathMap<V, K, A>& lhs, const PathMap<V, K, A>& rhs) {
// reinterpret lhs as the underlying vector type.
const folly::fbvector<std::pair<K, V>, A>& vector = lhs;
return vector != rhs;
}
}
}