// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Derived from google3/util/gtl/stl_util.h #ifndef BASE_STL_UTIL_H_ #define BASE_STL_UTIL_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base/logging.h" #include "base/optional.h" #include "base/template_util.h" namespace base { namespace internal { // Calls erase on iterators of matching elements and returns the number of // removed elements. template size_t IterateAndEraseIf(Container& container, Predicate pred) { size_t old_size = container.size(); for (auto it = container.begin(), last = container.end(); it != last;) { if (pred(*it)) it = container.erase(it); else ++it; } return old_size - container.size(); } template constexpr bool IsRandomAccessIter = std::is_same::iterator_category, std::random_access_iterator_tag>::value; // Utility type traits used for specializing base::Contains() below. template struct HasFindWithNpos : std::false_type {}; template struct HasFindWithNpos< Container, Element, void_t().find( std::declval()) != Container::npos)>> : std::true_type {}; template struct HasFindWithEnd : std::false_type {}; template struct HasFindWithEnd().find( std::declval()) != std::declval().end())>> : std::true_type {}; template struct HasContains : std::false_type {}; template struct HasContains().contains( std::declval()))>> : std::true_type {}; } // namespace internal // C++14 implementation of C++17's std::size(): // http://en.cppreference.com/w/cpp/iterator/size template constexpr auto size(const Container& c) -> decltype(c.size()) { return c.size(); } template constexpr size_t size(const T (&array)[N]) noexcept { return N; } // C++14 implementation of C++17's std::empty(): // http://en.cppreference.com/w/cpp/iterator/empty template constexpr auto empty(const Container& c) -> decltype(c.empty()) { return c.empty(); } template constexpr bool empty(const T (&array)[N]) noexcept { return false; } template constexpr bool empty(std::initializer_list il) noexcept { return il.size() == 0; } // C++14 implementation of C++17's std::data(): // http://en.cppreference.com/w/cpp/iterator/data template constexpr auto data(Container& c) -> decltype(c.data()) { return c.data(); } // std::basic_string::data() had no mutable overload prior to C++17 [1]. // Hence this overload is provided. // Note: str[0] is safe even for empty strings, as they are guaranteed to be // null-terminated [2]. // // [1] http://en.cppreference.com/w/cpp/string/basic_string/data // [2] http://en.cppreference.com/w/cpp/string/basic_string/operator_at template CharT* data(std::basic_string& str) { return std::addressof(str[0]); } template constexpr auto data(const Container& c) -> decltype(c.data()) { return c.data(); } template constexpr T* data(T (&array)[N]) noexcept { return array; } template constexpr const T* data(std::initializer_list il) noexcept { return il.begin(); } // std::array::data() was not constexpr prior to C++17 [1]. // Hence these overloads are provided. // // [1] https://en.cppreference.com/w/cpp/container/array/data template constexpr T* data(std::array& array) noexcept { return !array.empty() ? &array[0] : nullptr; } template constexpr const T* data(const std::array& array) noexcept { return !array.empty() ? &array[0] : nullptr; } // C++14 implementation of C++17's std::as_const(): // https://en.cppreference.com/w/cpp/utility/as_const template constexpr std::add_const_t& as_const(T& t) noexcept { return t; } template void as_const(const T&& t) = delete; // Returns a const reference to the underlying container of a container adapter. // Works for std::priority_queue, std::queue, and std::stack. template const typename A::container_type& GetUnderlyingContainer(const A& adapter) { struct ExposedAdapter : A { using A::c; }; return adapter.*&ExposedAdapter::c; } // Clears internal memory of an STL object. // STL clear()/reserve(0) does not always free internal memory allocated // This function uses swap/destructor to ensure the internal memory is freed. template void STLClearObject(T* obj) { T tmp; tmp.swap(*obj); // Sometimes "T tmp" allocates objects with memory (arena implementation?). // Hence using additional reserve(0) even if it doesn't always work. obj->reserve(0); } // Counts the number of instances of val in a container. template typename std::iterator_traits< typename Container::const_iterator>::difference_type STLCount(const Container& container, const T& val) { return std::count(container.begin(), container.end(), val); } // General purpose implementation to check if |container| contains |value|. template ::value && !internal::HasFindWithEnd::value && !internal::HasContains::value>* = nullptr> bool Contains(const Container& container, const Value& value) { using std::begin; using std::end; return std::find(begin(container), end(container), value) != end(container); } // Specialized Contains() implementation for when |container| has a find() // member function and a static npos member, but no contains() member function. template ::value && !internal::HasContains::value>* = nullptr> bool Contains(const Container& container, const Value& value) { return container.find(value) != Container::npos; } // Specialized Contains() implementation for when |container| has a find() // and end() member function, but no contains() member function. template ::value && !internal::HasContains::value>* = nullptr> bool Contains(const Container& container, const Value& value) { return container.find(value) != container.end(); } // Specialized Contains() implementation for when |container| has a contains() // member function. template < typename Container, typename Value, std::enable_if_t::value>* = nullptr> bool Contains(const Container& container, const Value& value) { return container.contains(value); } // O(1) implementation of const casting an iterator for any sequence, // associative or unordered associative container in the STL. // // Reference: https://stackoverflow.com/a/10669041 template >* = nullptr> constexpr auto ConstCastIterator(Container& c, ConstIter it) { return c.erase(it, it); } // Explicit overload for std::forward_list where erase() is named erase_after(). template constexpr auto ConstCastIterator( std::forward_list& c, typename std::forward_list::const_iterator it) { // The erase_after(it, it) trick used below does not work for libstdc++ [1], // thus we need a different way. // TODO(crbug.com/972541): Remove this workaround once libstdc++ is fixed on all // platforms. // // [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90857 #if defined(__GLIBCXX__) return c.insert_after(it, {}); #else return c.erase_after(it, it); #endif } // Specialized O(1) const casting for random access iterators. This is // necessary, because erase() is either not available (e.g. array-like // containers), or has O(n) complexity (e.g. std::deque or std::vector). template >* = nullptr> constexpr auto ConstCastIterator(Container& c, ConstIter it) { using std::begin; using std::cbegin; return begin(c) + (it - cbegin(c)); } namespace internal { template std::pair InsertOrAssignImpl(Map& map, Key&& key, Value&& value) { auto lower = map.lower_bound(key); if (lower != map.end() && !map.key_comp()(key, lower->first)) { // key already exists, perform assignment. lower->second = std::forward(value); return {lower, false}; } // key did not yet exist, insert it. return {map.emplace_hint(lower, std::forward(key), std::forward(value)), true}; } template typename Map::iterator InsertOrAssignImpl(Map& map, typename Map::const_iterator hint, Key&& key, Value&& value) { auto&& key_comp = map.key_comp(); if ((hint == map.begin() || key_comp(std::prev(hint)->first, key))) { if (hint == map.end() || key_comp(key, hint->first)) { // *(hint - 1) < key < *hint => key did not exist and hint is correct. return map.emplace_hint(hint, std::forward(key), std::forward(value)); } if (!key_comp(hint->first, key)) { // key == *hint => key already exists and hint is correct. auto mutable_hint = ConstCastIterator(map, hint); mutable_hint->second = std::forward(value); return mutable_hint; } } // hint was not helpful, dispatch to hintless version. return InsertOrAssignImpl(map, std::forward(key), std::forward(value)) .first; } template std::pair TryEmplaceImpl(Map& map, Key&& key, Args&&... args) { auto lower = map.lower_bound(key); if (lower != map.end() && !map.key_comp()(key, lower->first)) { // key already exists, do nothing. return {lower, false}; } // key did not yet exist, insert it. return {map.emplace_hint(lower, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple(std::forward(args)...)), true}; } template typename Map::iterator TryEmplaceImpl(Map& map, typename Map::const_iterator hint, Key&& key, Args&&... args) { auto&& key_comp = map.key_comp(); if ((hint == map.begin() || key_comp(std::prev(hint)->first, key))) { if (hint == map.end() || key_comp(key, hint->first)) { // *(hint - 1) < key < *hint => key did not exist and hint is correct. return map.emplace_hint( hint, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple(std::forward(args)...)); } if (!key_comp(hint->first, key)) { // key == *hint => no-op, return correct hint. return ConstCastIterator(map, hint); } } // hint was not helpful, dispatch to hintless version. return TryEmplaceImpl(map, std::forward(key), std::forward(args)...) .first; } } // namespace internal // Implementation of C++17's std::map::insert_or_assign as a free function. template std::pair InsertOrAssign(Map& map, const typename Map::key_type& key, Value&& value) { return internal::InsertOrAssignImpl(map, key, std::forward(value)); } template std::pair InsertOrAssign(Map& map, typename Map::key_type&& key, Value&& value) { return internal::InsertOrAssignImpl(map, std::move(key), std::forward(value)); } // Implementation of C++17's std::map::insert_or_assign with hint as a free // function. template typename Map::iterator InsertOrAssign(Map& map, typename Map::const_iterator hint, const typename Map::key_type& key, Value&& value) { return internal::InsertOrAssignImpl(map, hint, key, std::forward(value)); } template typename Map::iterator InsertOrAssign(Map& map, typename Map::const_iterator hint, typename Map::key_type&& key, Value&& value) { return internal::InsertOrAssignImpl(map, hint, std::move(key), std::forward(value)); } // Implementation of C++17's std::map::try_emplace as a free function. template std::pair TryEmplace(Map& map, const typename Map::key_type& key, Args&&... args) { return internal::TryEmplaceImpl(map, key, std::forward(args)...); } template std::pair TryEmplace(Map& map, typename Map::key_type&& key, Args&&... args) { return internal::TryEmplaceImpl(map, std::move(key), std::forward(args)...); } // Implementation of C++17's std::map::try_emplace with hint as a free // function. template typename Map::iterator TryEmplace(Map& map, typename Map::const_iterator hint, const typename Map::key_type& key, Args&&... args) { return internal::TryEmplaceImpl(map, hint, key, std::forward(args)...); } template typename Map::iterator TryEmplace(Map& map, typename Map::const_iterator hint, typename Map::key_type&& key, Args&&... args) { return internal::TryEmplaceImpl(map, hint, std::move(key), std::forward(args)...); } // Returns true if the container is sorted. template bool STLIsSorted(const Container& cont) { return std::is_sorted(std::begin(cont), std::end(cont)); } // Returns a new ResultType containing the difference of two sorted containers. template ResultType STLSetDifference(const Arg1& a1, const Arg2& a2) { DCHECK(STLIsSorted(a1)); DCHECK(STLIsSorted(a2)); ResultType difference; std::set_difference(a1.begin(), a1.end(), a2.begin(), a2.end(), std::inserter(difference, difference.end())); return difference; } // Returns a new ResultType containing the union of two sorted containers. template ResultType STLSetUnion(const Arg1& a1, const Arg2& a2) { DCHECK(STLIsSorted(a1)); DCHECK(STLIsSorted(a2)); ResultType result; std::set_union(a1.begin(), a1.end(), a2.begin(), a2.end(), std::inserter(result, result.end())); return result; } // Returns a new ResultType containing the intersection of two sorted // containers. template ResultType STLSetIntersection(const Arg1& a1, const Arg2& a2) { DCHECK(STLIsSorted(a1)); DCHECK(STLIsSorted(a2)); ResultType result; std::set_intersection(a1.begin(), a1.end(), a2.begin(), a2.end(), std::inserter(result, result.end())); return result; } // Returns true if the sorted container |a1| contains all elements of the sorted // container |a2|. template bool STLIncludes(const Arg1& a1, const Arg2& a2) { DCHECK(STLIsSorted(a1)); DCHECK(STLIsSorted(a2)); return std::includes(a1.begin(), a1.end(), a2.begin(), a2.end()); } // Erase/EraseIf are based on C++20's uniform container erasure API: // - https://eel.is/c++draft/libraryindex#:erase // - https://eel.is/c++draft/libraryindex#:erase_if // They provide a generic way to erase elements from a container. // The functions here implement these for the standard containers until those // functions are available in the C++ standard. // For Chromium containers overloads should be defined in their own headers // (like standard containers). // Note: there is no std::erase for standard associative containers so we don't // have it either. template size_t Erase(std::basic_string& container, const Value& value) { auto it = std::remove(container.begin(), container.end(), value); size_t removed = std::distance(it, container.end()); container.erase(it, container.end()); return removed; } template size_t EraseIf(std::basic_string& container, Predicate pred) { auto it = std::remove_if(container.begin(), container.end(), pred); size_t removed = std::distance(it, container.end()); container.erase(it, container.end()); return removed; } template size_t Erase(std::deque& container, const Value& value) { auto it = std::remove(container.begin(), container.end(), value); size_t removed = std::distance(it, container.end()); container.erase(it, container.end()); return removed; } template size_t EraseIf(std::deque& container, Predicate pred) { auto it = std::remove_if(container.begin(), container.end(), pred); size_t removed = std::distance(it, container.end()); container.erase(it, container.end()); return removed; } template size_t Erase(std::vector& container, const Value& value) { auto it = std::remove(container.begin(), container.end(), value); size_t removed = std::distance(it, container.end()); container.erase(it, container.end()); return removed; } template size_t EraseIf(std::vector& container, Predicate pred) { auto it = std::remove_if(container.begin(), container.end(), pred); size_t removed = std::distance(it, container.end()); container.erase(it, container.end()); return removed; } template size_t Erase(std::forward_list& container, const Value& value) { // Unlike std::forward_list::remove, this function template accepts // heterogeneous types and does not force a conversion to the container's // value type before invoking the == operator. return EraseIf(container, [&](const T& cur) { return cur == value; }); } template size_t EraseIf(std::forward_list& container, Predicate pred) { // Note: std::forward_list does not have a size() API, thus we need to use the // O(n) std::distance work-around. However, given that EraseIf is O(n) // already, this should not make a big difference. size_t old_size = std::distance(container.begin(), container.end()); container.remove_if(pred); return old_size - std::distance(container.begin(), container.end()); } template size_t Erase(std::list& container, const Value& value) { // Unlike std::list::remove, this function template accepts heterogeneous // types and does not force a conversion to the container's value type before // invoking the == operator. return EraseIf(container, [&](const T& cur) { return cur == value; }); } template size_t EraseIf(std::list& container, Predicate pred) { size_t old_size = container.size(); container.remove_if(pred); return old_size - container.size(); } template size_t EraseIf(std::map& container, Predicate pred) { return internal::IterateAndEraseIf(container, pred); } template size_t EraseIf(std::multimap& container, Predicate pred) { return internal::IterateAndEraseIf(container, pred); } template size_t EraseIf(std::set& container, Predicate pred) { return internal::IterateAndEraseIf(container, pred); } template size_t EraseIf(std::multiset& container, Predicate pred) { return internal::IterateAndEraseIf(container, pred); } template size_t EraseIf(std::unordered_map& container, Predicate pred) { return internal::IterateAndEraseIf(container, pred); } template size_t EraseIf( std::unordered_multimap& container, Predicate pred) { return internal::IterateAndEraseIf(container, pred); } template size_t EraseIf(std::unordered_set& container, Predicate pred) { return internal::IterateAndEraseIf(container, pred); } template size_t EraseIf( std::unordered_multiset& container, Predicate pred) { return internal::IterateAndEraseIf(container, pred); } // A helper class to be used as the predicate with |EraseIf| to implement // in-place set intersection. Helps implement the algorithm of going through // each container an element at a time, erasing elements from the first // container if they aren't in the second container. Requires each container be // sorted. Note that the logic below appears inverted since it is returning // whether an element should be erased. template class IsNotIn { public: explicit IsNotIn(const Collection& collection) : i_(collection.begin()), end_(collection.end()) {} bool operator()(const typename Collection::value_type& x) { while (i_ != end_ && *i_ < x) ++i_; if (i_ == end_) return true; if (*i_ == x) { ++i_; return false; } return true; } private: typename Collection::const_iterator i_; const typename Collection::const_iterator end_; }; // Helper for returning the optional value's address, or nullptr. template T* OptionalOrNullptr(base::Optional& optional) { return optional.has_value() ? &optional.value() : nullptr; } template const T* OptionalOrNullptr(const base::Optional& optional) { return optional.has_value() ? &optional.value() : nullptr; } } // namespace base #endif // BASE_STL_UTIL_H_