Files
allwpilib/wpiutil/src/main/native/include/wpi/util/StringMap.hpp
2025-11-08 14:07:00 -08:00

779 lines
30 KiB
C++

// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <functional>
#include <initializer_list>
#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
namespace wpi::util {
/**
* StringMap is a sorted associative container that contains key-value pairs
* with unique string keys. Keys are sorted in the same order as std::string's
* are compared. Search, removal, and insertion operations have logarithmic
* complexity. The underlying implementation is std::map<std::string, T>.
*/
template <typename T,
typename Allocator = std::allocator<std::pair<const std::string, T>>>
class StringMap : public std::map<std::string, T, std::less<>, Allocator> {
public:
using map_type = typename std::map<std::string, T, std::less<>>;
using key_type = typename map_type::key_type;
using mapped_type = typename map_type::mapped_type;
using value_type = typename map_type::value_type;
using size_type = typename map_type::size_type;
using difference_type = typename map_type::difference_type;
using key_compare = typename map_type::key_compare;
using allocator_type = typename map_type::allocator_type;
using reference = typename map_type::reference;
using const_reference = typename map_type::const_reference;
using pointer = typename map_type::pointer;
using const_pointer = typename map_type::const_pointer;
using iterator = typename map_type::iterator;
using const_iterator = typename map_type::const_iterator;
using reverse_iterator = typename map_type::reverse_iterator;
using const_reverse_iterator = typename map_type::const_reverse_iterator;
using node_type = typename map_type::node_type;
using insert_return_type = typename map_type::insert_return_type;
/** Constructs an empty container. */
StringMap() = default;
/**
* Constructs an empty container.
*
* @param alloc allocator to use for all memory allocations of this container
*/
explicit StringMap(const Allocator& alloc) : map_type{alloc} {}
/**
* Constructs the container with the contents of the range [first, last). If
* multiple elements in the range have keys that compare equivalent, it is
* unspecified which element is inserted. If [first, last) is not a valid
* range, the behavior is undefined.
*
* @param first start of the range to copy the elements from
* @param last end of the range to copy the elements from
* @param alloc allocator to use for all memory allocations of this container
*/
template <typename InputIt>
StringMap(InputIt first, InputIt last, const Allocator& alloc = Allocator())
: map_type{first, last, alloc} {}
/** Copy constructor. */
StringMap(const StringMap&) = default;
/**
* Copy constructor.
*
* @param other another container to be used as source to initialize the
* elements of the container with
* @param alloc allocator to use for all memory allocations of this container
*/
StringMap(const StringMap& other, const Allocator& alloc)
: map_type{other, alloc} {}
/** Move constructor. */
StringMap(StringMap&&) = default;
/**
* Move constructor.
*
* @param other another container to be used as source to initialize the
* elements of the container with
* @param alloc allocator to use for all memory allocations of this container
*/
StringMap(StringMap&& other, const Allocator& alloc)
: map_type{other, alloc} {}
/**
* Initializer-list constructor. Construct the container with the contents of
* the initializer list init. If multiple elements in the range have keys
* that compare equal, it is unspecified which element is inserted.
*
* @param init initializer list to initialize the elements of the container
* with
* @param alloc allocator to use for all memory allocations of this container
*/
// NOLINTNEXTLINE (google-explicit-constructor)
StringMap(std::initializer_list<value_type> init,
const Allocator& alloc = Allocator())
: map_type{init, alloc} {}
/** Copy assignment operator. */
StringMap& operator=(const StringMap&) = default;
/** Move assignment operator. */
StringMap& operator=(StringMap&&) noexcept(
std::allocator_traits<Allocator>::is_always_equal::value &&
std::is_nothrow_move_assignable<std::less<>>::value) = default;
/**
* Replaces the contents with those identified by initializer list ilist.
*
* @param ilist initializer list to use as data source
*/
StringMap& operator=(std::initializer_list<value_type> ilist) {
map_type::operator=(ilist);
return *this;
}
/**
* Returns a reference to the mapped value of the element with the specified
* key. If no such element exists, an exception of type std::out_of_range is
* thrown.
*
* @param key the key of the element to find
* @return A reference to the mapped value of the requested element.
*/
T& at(const std::string& key) { return map_type::at(key); }
/**
* Returns a reference to the mapped value of the element with the specified
* key. If no such element exists, an exception of type std::out_of_range is
* thrown.
*
* @param key the key of the element to find
* @return A reference to the mapped value of the requested element.
*/
const T& at(const std::string& key) const { return map_type::at(key); }
/**
* Returns a reference to the mapped value of the element with the specified
* key. If no such element exists, an exception of type std::out_of_range is
* thrown.
*
* @param key the key of the element to find
* @return A reference to the mapped value of the requested element.
*/
T& at(const char* key) { return at(std::string_view{key}); }
/**
* Returns a reference to the mapped value of the element with the specified
* key. If no such element exists, an exception of type std::out_of_range is
* thrown.
*
* @param key the key of the element to find
* @return A reference to the mapped value of the requested element.
*/
const T& at(const char* key) const { return at(std::string_view{key}); }
/**
* Returns a reference to the mapped value of the element with the specified
* key. If no such element exists, an exception of type std::out_of_range is
* thrown.
*
* @param key the key of the element to find
* @return A reference to the mapped value of the requested element.
*/
T& at(std::string_view key) {
#ifdef __cpp_lib_associative_heterogeneous_insertion
return map_type::at(key);
#else
auto it = find(key);
if (it == this->end()) {
throw std::out_of_range{std::string{key}};
}
return it->second;
#endif
}
/**
* Returns a reference to the mapped value of the element with the specified
* key. If no such element exists, an exception of type std::out_of_range is
* thrown.
*
* @param key the key of the element to find
* @return A reference to the mapped value of the requested element.
*/
const T& at(std::string_view key) const {
#ifdef __cpp_lib_associative_heterogeneous_insertion
return map_type::at(key);
#else
auto it = find(key);
if (it == this->end()) {
throw std::out_of_range{std::string{key}};
}
return it->second;
#endif
}
/**
* Returns a reference to the value that is mapped to a key, performing an
* insertion if such a key does not exist.
*
* @param key the key of the element to find
* @return A reference to the mapped value of the new element if no element
* with key key existed. Otherwise, a reference to the mapped value
* of the existing element whose key is equal to key.
*/
T& operator[](const std::string& key) { return map_type::operator[](key); }
/**
* Returns a reference to the value that is mapped to a key, performing an
* insertion if such a key does not exist.
*
* @param key the key of the element to find
* @return A reference to the mapped value of the new element if no element
* with key key existed. Otherwise, a reference to the mapped value
* of the existing element whose key is equal to key.
*/
T& operator[](std::string&& key) {
return map_type::operator[](std::move(key));
}
/**
* Returns a reference to the value that is mapped to a key, performing an
* insertion if such a key does not exist.
*
* @param key the key of the element to find
* @return A reference to the mapped value of the new element if no element
* with key key existed. Otherwise, a reference to the mapped value
* of the existing element whose key is equal to key.
*/
T& operator[](const char* key) { return operator[](std::string_view{key}); }
/**
* Returns a reference to the value that is mapped to a key, performing an
* insertion if such a key does not exist.
*
* @param key the key of the element to find
* @return A reference to the mapped value of the new element if no element
* with key key existed. Otherwise, a reference to the mapped value
* of the existing element whose key is equal to key.
*/
T& operator[](std::string_view key) { return try_emplace(key).first->second; }
/**
* If a key equal to key already exists in the container, assigns obj to the
* mapped type corresponding to that key. If the key does not exist, inserts
* the value as if by calling insert().
*
* @param key the key used both to look up and to insert if not found
* @param obj the value to insert or assign
* @return The bool component is true if the insertion took place and false if
* the assignment took place. The iterator component is pointing at
* the element that was inserted or updated.
*/
template <typename M>
std::pair<iterator, bool> insert_or_assign(std::string&& key, M&& obj) {
return map_type::insert_or_assign(std::move(key), std::forward<M>(obj));
}
/**
* If a key equal to key already exists in the container, assigns obj to the
* mapped type corresponding to that key. If the key does not exist, inserts
* the value as if by calling insert().
*
* @param key the key used both to look up and to insert if not found
* @param obj the value to insert or assign
* @return The bool component is true if the insertion took place and false if
* the assignment took place. The iterator component is pointing at
* the element that was inserted or updated.
*/
template <typename M>
std::pair<iterator, bool> insert_or_assign(const char* key, M&& obj) {
return insert_or_assign(std::string_view{key}, std::forward<M>(obj));
}
/**
* If a key equal to key already exists in the container, assigns obj to the
* mapped type corresponding to that key. If the key does not exist, inserts
* the value as if by calling insert().
*
* @param key the key used both to look up and to insert if not found
* @param obj the value to insert or assign
* @return The bool component is true if the insertion took place and false if
* the assignment took place. The iterator component is pointing at
* the element that was inserted or updated.
*/
template <typename M>
std::pair<iterator, bool> insert_or_assign(std::string_view key, M&& obj) {
#ifdef __cpp_lib_associative_heterogeneous_insertion
return map_type::insert_or_assign(key, std::forward<M>(obj));
#else
auto it = lower_bound(key);
if (it != this->end() && it->first == key) {
it->second = std::forward<M>(obj);
return {it, false};
} else {
return {this->insert(it, {std::string{key}, std::forward<M>(obj)}), true};
}
#endif
}
/**
* If a key equal to key already exists in the container, assigns obj to the
* mapped type corresponding to that key. If the key does not exist, inserts
* the value as if by calling insert().
*
* @param hint iterator to the position before which the new element will be
* inserted
* @param key the key used both to look up and to insert if not found
* @param obj the value to insert or assign
* @return Iterator pointing at the element that was inserted or updated.
*/
template <typename M>
iterator insert_or_assign(const_iterator hint, std::string&& key, M&& obj) {
return map_type::insert_or_assign(hint, std::move(key),
std::forward<M>(obj));
}
/**
* If a key equal to key already exists in the container, assigns obj to the
* mapped type corresponding to that key. If the key does not exist, inserts
* the value as if by calling insert().
*
* @param hint iterator to the position before which the new element will be
* inserted
* @param key the key used both to look up and to insert if not found
* @param obj the value to insert or assign
* @return Iterator pointing at the element that was inserted or updated.
*/
template <typename M>
iterator insert_or_assign(const_iterator hint, const char* key, M&& obj) {
return insert_or_assign(hint, std::string_view{key}, std::forward<M>(obj));
}
/**
* If a key equal to key already exists in the container, assigns obj to the
* mapped type corresponding to that key. If the key does not exist, inserts
* the value as if by calling insert().
*
* @param hint iterator to the position before which the new element will be
* inserted
* @param key the key used both to look up and to insert if not found
* @param obj the value to insert or assign
* @return Iterator pointing at the element that was inserted or updated.
*/
template <typename M>
iterator insert_or_assign(const_iterator hint, std::string_view key,
M&& obj) {
#ifdef __cpp_lib_associative_heterogeneous_insertion
return map_type::insert_or_assign(hint, key, std::forward<M>(obj));
#else
return map_type::insert_or_assign(hint, std::string{key},
std::forward<M>(obj));
#endif
}
/**
* Inserts a new element into the container constructed in-place with the
* given args, if there is no element with the key in the container.
*
* No iterators or references are invalidated.
*
* @param key the key used both to look up and to insert if not found
* @param args arguments to forward to the constructor of the element
* @return A pair consisting of an interator to the inserted element (or to
* the element that prevented the insertion) and a bool value set to
* true if and only if the insertion took place.
*/
template <typename... Args>
std::pair<iterator, bool> emplace(std::string&& key, Args&&... args) {
return map_type::emplace(std::move(key), std::forward<Args>(args)...);
}
/**
* Inserts a new element into the container constructed in-place with the
* given args, if there is no element with the key in the container.
*
* No iterators or references are invalidated.
*
* @param key the key used both to look up and to insert if not found
* @param args arguments to forward to the constructor of the element
* @return A pair consisting of an interator to the inserted element (or to
* the element that prevented the insertion) and a bool value set to
* true if and only if the insertion took place.
*/
template <typename... Args>
std::pair<iterator, bool> emplace(const char* key, Args&&... args) {
return emplace(std::string_view{key}, std::forward<Args>(args)...);
}
/**
* Inserts a new element into the container constructed in-place with the
* given args, if there is no element with the key in the container.
*
* No iterators or references are invalidated.
*
* @param key the key used both to look up and to insert if not found
* @param args arguments to forward to the constructor of the element
* @return A pair consisting of an interator to the inserted element (or to
* the element that prevented the insertion) and a bool value set to
* true if and only if the insertion took place.
*/
template <typename... Args>
std::pair<iterator, bool> emplace(std::string_view key, Args&&... args) {
return try_emplace(key, std::forward<Args>(args)...);
}
/**
* If a key equal to key already exists in the container, does nothing.
* Otherwise, inserts a new element into the container with key key and value
* constructed with args.
*
* @param key the key used both to look up and to insert if not found
* @param args arguments to forward to the constructor of the element
* @return A pair consisting of an interator to the inserted element (or to
* the element that prevented the insertion) and a bool value set to
* true if and only if the insertion took place.
*/
template <typename... Args>
std::pair<iterator, bool> try_emplace(std::string&& key, Args&&... args) {
return map_type::try_emplace(std::move(key), std::forward<Args>(args)...);
}
/**
* If a key equal to key already exists in the container, does nothing.
* Otherwise, inserts a new element into the container with key key and value
* constructed with args.
*
* @param key the key used both to look up and to insert if not found
* @param args arguments to forward to the constructor of the element
* @return A pair consisting of an interator to the inserted element (or to
* the element that prevented the insertion) and a bool value set to
* true if and only if the insertion took place.
*/
template <typename... Args>
std::pair<iterator, bool> try_emplace(const char* key, Args&&... args) {
return try_emplace(std::string_view{key}, std::forward<Args>(args)...);
}
/**
* If a key equal to key already exists in the container, does nothing.
* Otherwise, inserts a new element into the container with key key and value
* constructed with args.
*
* @param key the key used both to look up and to insert if not found
* @param args arguments to forward to the constructor of the element
* @return A pair consisting of an interator to the inserted element (or to
* the element that prevented the insertion) and a bool value set to
* true if and only if the insertion took place.
*/
template <typename... Args>
std::pair<iterator, bool> try_emplace(std::string_view key, Args&&... args) {
#ifdef __cpp_lib_associative_heterogeneous_insertion
return map_type::try_emplace(key, std::forward<Args>(args)...);
#else
auto it = lower_bound(key);
if (it != this->end() && it->first == key) {
return {it, false};
} else {
return {try_emplace(it, key, std::forward<Args>(args)...), true};
}
#endif
}
/**
* If a key equal to key already exists in the container, does nothing.
* Otherwise, inserts a new element into the container with key key and value
* constructed with args.
*
* @param hint iterator to the position before which the new element will be
* inserted
* @param key the key used both to look up and to insert if not found
* @param args arguments to forward to the constructor of the element
* @return An interator to the inserted element, or to the element that
* prevented the insertion.
*/
template <typename... Args>
iterator try_emplace(const_iterator hint, std::string&& key, Args&&... args) {
return map_type::try_emplace(hint, std::move(key),
std::forward<Args>(args)...);
}
/**
* If a key equal to key already exists in the container, does nothing.
* Otherwise, inserts a new element into the container with key key and value
* constructed with args.
*
* @param hint iterator to the position before which the new element will be
* inserted
* @param key the key used both to look up and to insert if not found
* @param args arguments to forward to the constructor of the element
* @return An interator to the inserted element, or to the element that
* prevented the insertion.
*/
template <typename... Args>
iterator try_emplace(const_iterator hint, const char* key, Args&&... args) {
return try_emplace(hint, std::string_view{key},
std::forward<Args>(args)...);
}
/**
* If a key equal to key already exists in the container, does nothing.
* Otherwise, inserts a new element into the container with key key and value
* constructed with args.
*
* @param hint iterator to the position before which the new element will be
* inserted
* @param key the key used both to look up and to insert if not found
* @param args arguments to forward to the constructor of the element
* @return An interator to the inserted element, or to the element that
* prevented the insertion.
*/
template <typename... Args>
iterator try_emplace(const_iterator hint, std::string_view key,
Args&&... args) {
#ifdef __cpp_lib_associative_heterogeneous_insertion
return map_type::try_emplace(hint, key, std::forward<Args>(args)...);
#else
return map_type::try_emplace(hint, std::string{key},
std::forward<Args>(args)...);
#endif
}
/**
* Removes the element at pos.
*
* @param pos iterator to the element to remove
* @return Iterator following the removed element.
*/
iterator erase(iterator pos) { return map_type::erase(pos); }
/**
* Removes the element at pos.
*
* @param pos iterator to the element to remove
* @return Iterator following the removed element.
*/
iterator erase(const_iterator pos) { return map_type::erase(pos); }
/**
* Removes the elements in the range [first, last), which must be a valid
* range in this.
*
* @param first Start of the range of elements to remove
* @param last End of the range of elements to remove
* @return Iterator following the last removed element.
*/
iterator erase(iterator first, iterator last) {
return map_type::erase(first, last);
}
/**
* Removes the elements in the range [first, last), which must be a valid
* range in this.
*
* @param first Start of the range of elements to remove
* @param last End of the range of elements to remove
* @return Iterator following the last removed element.
*/
iterator erase(const_iterator first, const_iterator last) {
return map_type::erase(first, last);
}
/**
* Removes the element (if one exists) with the key equal to key.
*
* @param key key value of the elements to remove
* @return Number of elements removed (0 or 1).
*/
size_type erase(std::string_view key) {
#ifdef __cpp_lib_associative_heterogeneous_erasure
return map_type::erase(key);
#else
auto it = find(key);
if (it == this->end()) {
return 0;
}
this->erase(it);
return 1;
#endif
}
/**
* Exchanges the contents of the container with those of other. Does not
* invoke any move, copy, or swap operations on individual elements.
*
* All iterators and references remain valid. The end() iterator is
* invalidated.
*
* @param other container to exchange the contents with
*/
void swap(StringMap& other) noexcept(
std::allocator_traits<Allocator>::is_always_equal::value &&
std::is_nothrow_swappable<std::less<>>::value) {
map_type::swap(other);
}
/**
* Unlinks the node that contains the element pointed to by position and
* returns a node handle that owns it.
*
* @param position a valid iterator into this container
* @return A node handle that owns the extracted element
*/
node_type extract(const_iterator position) {
return map_type::extract(position);
}
/**
* If the container has an element with key equal to key, unlinks the node
* that contains that element from the container and returns a node handle
* that owns it. Otherwise, returns an empty node handle.
*
* @param key a key to identify the node to be extracted
* @return A node handle that owns the extracted element, or empty node handle
* in case the element is not found.
*/
node_type extract(std::string_view key) {
#ifdef __cpp_lib_associative_heterogeneous_insertion
return map_type::extract(key);
#else
auto it = find(key);
if (it == this->end()) {
return {};
}
return extract(it);
#endif
}
/**
* Returns the number of elements with key that equals the specified argument.
*
* @param key key value of the elements to count
* @return Number of elements with key that equals key (either 0 or 1).
*/
size_type count(std::string_view key) const { return map_type::count(key); }
/**
* Finds an element with key equal to key.
*
* @param key key value of the element to search for
* @return An iterator to the requested element. If no such element is found,
* past-the-end (see end()) iterator is returned.
*/
iterator find(std::string_view key) { return map_type::find(key); }
/**
* Finds an element with key equal to key.
*
* @param key key value of the element to search for
* @return An iterator to the requested element. If no such element is found,
* past-the-end (see end()) iterator is returned.
*/
const_iterator find(std::string_view key) const {
return map_type::find(key);
}
/**
* Checks if there is an element with key equal to key in the container.
*
* @param key key value of the element to search for
* @return true if there is such an element, otherwise false
*/
bool contains(std::string_view key) const { return map_type::contains(key); }
/**
* Returns a range containing all elements with the given key in the
* container. The range is defined by two iterators, one pointing to the
* first element that is not less than key and another pointing to the first
* element greater than key. Alternatively, the first iterator may be
* obtained with lower_bound(), and the second with upper_bound().
*
* @param key key value to compare the elements to
* @return std::pair containing a pair of iterators defining the wanted range:
* the first pointing to the first element that is not less than key
* and the second pointing to the first element greater than key. If
* there are no elements not less than key, past-the-end (see end())
* iterator is returned as the first element. Similarly if there are
* no elements greater than key, past-the-end iterator is returned as
* the second element.
*/
std::pair<iterator, iterator> equal_range(std::string_view key) {
return map_type::equal_range(key);
}
/**
* Returns a range containing all elements with the given key in the
* container. The range is defined by two iterators, one pointing to the
* first element that is not less than key and another pointing to the first
* element greater than key. Alternatively, the first iterator may be
* obtained with lower_bound(), and the second with upper_bound().
*
* @param key key value to compare the elements to
* @return std::pair containing a pair of iterators defining the wanted range:
* the first pointing to the first element that is not less than key
* and the second pointing to the first element greater than key. If
* there are no elements not less than key, past-the-end (see end())
* iterator is returned as the first element. Similarly if there are
* no elements greater than key, past-the-end iterator is returned as
* the second element.
*/
std::pair<const_iterator, const_iterator> equal_range(
std::string_view key) const {
return map_type::equal_range(key);
}
/**
* Returns an iterator pointing to the first element that is not less than
* (i.e. greater or equal to) key.
*
* @param key key value to compare the elements to
* @return Iterator pointing to the first element that is not less than key.
* If no such element is found, a past-the-end iterator (see end()) is
* returned.
*/
iterator lower_bound(std::string_view key) {
return map_type::lower_bound(key);
}
/**
* Returns an iterator pointing to the first element that is not less than
* (i.e. greater or equal to) key.
*
* @param key key value to compare the elements to
* @return Iterator pointing to the first element that is not less than key.
* If no such element is found, a past-the-end iterator (see end()) is
* returned.
*/
const_iterator lower_bound(std::string_view key) const {
return map_type::lower_bound(key);
}
/**
* Returns an interator pointing to the first element that is greater than
* key.
*
* @param key key value to compare the elements to
* @return Iterator pointing to the first element that is greater than key.
* If no such element is found, past-the-end (see end()) iterator is
* returned.
*/
iterator upper_bound(std::string_view key) {
return map_type::upper_bound(key);
}
/**
* Returns an interator pointing to the first element that is greater than
* key.
*
* @param key key value to compare the elements to
* @return Iterator pointing to the first element that is greater than key.
* If no such element is found, past-the-end (see end()) iterator is
* returned.
*/
const_iterator upper_bound(std::string_view key) const {
return map_type::upper_bound(key);
}
};
} // namespace wpi::util
namespace std {
template <typename T>
inline void swap(wpi::util::StringMap<T>& lhs, wpi::util::StringMap<T>& rhs) {
lhs.swap(rhs);
}
} // namespace std