blob: 1221a26d7a65fd765d05794cd97d8f1376bb46d9 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FBL_INTRUSIVE_CONTAINER_UTILS_H_
#define FBL_INTRUSIVE_CONTAINER_UTILS_H_
#include <fbl/intrusive_pointer_traits.h>
#include <type_traits>
namespace fbl {
// DefaultKeyedObjectTraits defines a default implementation of traits used to
// manage objects stored in associative containers such as hash-tables and
// trees.
//
// At a minimum, a class or a struct which is to be used to define the
// traits of a keyed object must define the following public members.
//
// GetKey : A static method which takes a constant reference to an object (the
// type of which is infered from PtrType) and returns a KeyType
// instance corresponding to the key for an object.
// LessThan : A static method which takes two keys (key1 and key2) and returns
// true if-and-only-if key1 is considered to be less than key2 for
// sorting purposes.
// EqualTo : A static method which takes two keys (key1 and key2) and returns
// true if-and-only-if key1 is considered to be equal to key2.
//
// Rules for keys:
// ++ The type of key returned by GetKey must be compatible with the key which
// was specified for the container.
// ++ The key for an object must remain constant for as long as the object is
// contained within a container.
// ++ When comparing keys, comparisons must obey basic transative and
// commutative properties. That is to say...
// LessThan(A, B) and LessThan(B, C) implies LessThan(A, C)
// EqualTo(A, B) and EqualTo(B, C) implies EqualTo(A, C)
// EqualTo(A, B) if-and-only-if EqualTo(B, A)
// LessThan(A, B) if-and-only-if EqualTo(B, A) or (not LessThan(B, A))
//
// DefaultKeyedObjectTraits is a helper class which allows an object to be
// treated as a keyed-object by implementing a const GetKey method which returns
// a key of the appropriate type. The key type must be compatible with the
// container key type, and must have definitions of the < and == operators for
// the purpose of generating implementation of LessThan and EqualTo.
template <typename KeyType, typename ObjType>
struct DefaultKeyedObjectTraits {
static KeyType GetKey(const ObjType& obj) { return obj.GetKey(); }
static bool LessThan(const KeyType& key1, const KeyType& key2) { return key1 < key2; }
static bool EqualTo (const KeyType& key1, const KeyType& key2) { return key1 == key2; }
};
} // namespace fbl
namespace fbl::internal {
// DirectEraseUtils
//
// A utility class used by HashTable to implement an O(n) or O(k) direct erase
// operation depending on whether or not the HashTable's bucket type supports
// O(k) erase.
template <typename ContainerType, typename Enable = void>
struct DirectEraseUtils;
template <typename ContainerType>
struct DirectEraseUtils<
ContainerType,
std::enable_if_t<ContainerType::SupportsConstantOrderErase == false, void>> {
using PtrTraits = typename ContainerType::PtrTraits;
using PtrType = typename PtrTraits::PtrType;
using ValueType = typename PtrTraits::ValueType;
static PtrType erase(ContainerType& container, ValueType& obj) {
return container.erase_if(
[&obj](const ValueType& other) -> bool {
return &obj == &other;
});
}
};
template <typename ContainerType>
struct DirectEraseUtils<
ContainerType,
std::enable_if_t<ContainerType::SupportsConstantOrderErase == true, void>> {
using PtrTraits = typename ContainerType::PtrTraits;
using PtrType = typename PtrTraits::PtrType;
using ValueType = typename PtrTraits::ValueType;
static PtrType erase(ContainerType& container, ValueType& obj) {
return container.erase(obj);
}
};
// KeyEraseUtils
//
// A utility class used by HashTable to implement an O(n) or O(k) erase-by-key
// operation depending on whether or not the HashTable's bucket type is
// associative or not.
template <typename ContainerType, typename KeyTraits, typename Enable = void>
struct KeyEraseUtils;
template <typename ContainerType, typename KeyTraits>
struct KeyEraseUtils<
ContainerType,
KeyTraits,
std::enable_if_t<ContainerType::IsAssociative == false, void>> {
using PtrTraits = typename ContainerType::PtrTraits;
using PtrType = typename PtrTraits::PtrType;
using ValueType = typename PtrTraits::ValueType;
template <typename KeyType>
static PtrType erase(ContainerType& container, const KeyType& key) {
return container.erase_if(
[key](const ValueType& other) -> bool {
return KeyTraits::EqualTo(key, KeyTraits::GetKey(other));
});
}
};
template <typename ContainerType, typename KeyTraits>
struct KeyEraseUtils<
ContainerType,
KeyTraits,
std::enable_if_t<ContainerType::IsAssociative == true, void>> {
using PtrTraits = typename ContainerType::PtrTraits;
using PtrType = typename PtrTraits::PtrType;
template <typename KeyType>
static PtrType erase(ContainerType& container, const KeyType& key) {
return container.erase(key);
}
};
// Swaps two plain old data types with size no greater than 64 bits.
template <typename T, typename = std::enable_if_t<std::is_pod_v<T> && (sizeof(T) <= 8)>>
inline void Swap(T& a, T& b) noexcept {
T tmp = a;
a = b;
b = tmp;
}
// Notes on container sentinels:
//
// Intrusive container implementations employ a slightly tricky pattern where
// sentinel values are used in place of nullptr in various places in the
// internal data structure in order to make some operations a bit easier.
// Generally speaking, a sentinel pointer is a pointer to a container with the
// sentinel bit set. It is cast and stored in the container's data structure as
// a pointer to an element which the container contains, even though it is
// actually a slightly damaged pointer to the container itself.
//
// An example of where this is used is in the doubly linked list implementation.
// The final element in the list holds the container's sentinel value instead of
// nullptr or a pointer to the head of the list. When an iterator hits the end
// of the list, it knows that it is at the end (because the sentinel bit is set)
// but can still get back to the list itself (by clearing the sentinel bit in
// the pointer) without needing to store an explicit pointer to the list itself.
//
// Care must be taken when using sentinel values. They are *not* valid pointers
// and must never be dereferenced, recovered into an managed representation, or
// returned to a user. In addition, it is essential that a legitimate pointer
// to a container never need to set the sentinel bit. Currently, bit 0 is being
// used because it should never be possible to have a proper container instance
// which is odd-aligned.
constexpr uintptr_t kContainerSentinelBit = 1U;
// Create a sentinel pointer from a raw pointer, converting it to the specified
// type in the process.
template <typename T, typename U, typename = std::enable_if_t<std::is_pointer_v<T>>>
constexpr T make_sentinel(U* ptr) {
return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(ptr) |
kContainerSentinelBit);
}
template <typename T, typename = std::enable_if_t<std::is_pointer_v<T>>>
constexpr T make_sentinel(decltype(nullptr)) {
return reinterpret_cast<T>(kContainerSentinelBit);
}
// Turn a sentinel pointer back into a normal pointer, automatically
// re-interpreting its type in the process.
template <typename T, typename U, typename = std::enable_if_t<std::is_pointer_v<T>>>
constexpr T unmake_sentinel(U* sentinel) {
return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(sentinel) &
~kContainerSentinelBit);
}
// Test to see if a pointer is a sentinel pointer.
template <typename T>
constexpr bool is_sentinel_ptr(const T* ptr) {
return (reinterpret_cast<uintptr_t>(ptr) & kContainerSentinelBit) != 0;
}
// Test to see if a pointer (which may be a sentinel) is valid. Valid in this
// context means that the pointer is not null, and is not a sentinel.
template <typename T>
constexpr bool valid_sentinel_ptr(const T* ptr) {
return ptr && !is_sentinel_ptr(ptr);
}
} // namespace fbl::internal
#endif // FBL_INTRUSIVE_CONTAINER_UTILS_H_