| // Copyright 2017-2022 The Verible Authors. |
| // |
| // 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. |
| |
| #ifndef VERIBLE_COMMON_UTIL_CONTAINER_PROXY_H_ |
| #define VERIBLE_COMMON_UTIL_CONTAINER_PROXY_H_ |
| |
| #include <initializer_list> |
| #include <iterator> |
| #include <type_traits> |
| #include <utility> |
| |
| namespace verible { |
| |
| // CRTP base class for creating modification tracking STL container proxies. |
| // |
| // The class allows safe exposition of a STL container to an user in cases where |
| // changes to the container must be pre-/post-processed. This is done through |
| // implementation of several optional event handling methods in a derived class. |
| // Event handling methods that are not implemented do not introduce overhead. |
| // |
| // Example use case: List of child nodes in a tree which sets parent when |
| // a child is inserted. |
| // |
| // Note that the proxy only tracks container changes. It does not track |
| // individual elements. |
| // |
| // Derived class interface |
| // ----------------------- |
| // |
| // - Mandatory: |
| // - `ContainerType& underlying_container()`, |
| // `const ContainerType& underlying_container() const`: |
| // Returns reference to the wrapped container. |
| // |
| // - Optional: |
| // - `void ElementsInserted(iterator first, iterator last)`: |
| // Called when new elements were inserted. `first` and `last` (exclusive) |
| // refer to inserted elements. |
| // - `void ElementsBeingRemoved(iterator first, iterator last)`: |
| // Called just before removing elements from `first` to `last` (exclusive). |
| // - `void ElementsBeingReplaced()`: |
| // Called when all elements are going to be replaced (due to assignment). |
| // - `void ElementsWereReplaced()`: |
| // Called just after all elements were replaced. |
| // |
| // Usage |
| // ----- |
| // |
| // 1. Subclass: |
| // |
| // using ContainerType = std::vector<int> |
| // class MyProxy: private ContainerProxyBase<MyProxy, ContainerType> { |
| // using Base = ContainerProxyBase<MyProxy, ContainerType>; |
| // friend Base; |
| // // ... |
| // }; |
| // |
| // 2. Implement mandatory interface: |
| // |
| // private: |
| // ContainerType& underlying_container() { |
| // return container_; |
| // } |
| // const ContainerType& underlying_container() const { |
| // return container_; |
| // } |
| // // ... |
| // ContainerType container_; // not a part of the interface |
| // |
| // 3. Inherit public interface (all or only some members): |
| // |
| // public: |
| // using typename Base::container_type; |
| // using typename Base::value_type; |
| // using typename Base::reference; |
| // using typename Base::const_reference; |
| // // ... |
| // using Base::begin; |
| // using Base::end; |
| // // ... |
| // |
| // Note: inheriting (or implementing) `void swap(MyProxy&)` will also provide |
| // standalone function `void swap(MyProxy&, MyProxy&)`. |
| // |
| // 4. Implement optional interface: |
| // |
| // private: |
| // void ElementsInserted(iterator first, iterator last) { |
| // /* ... */ |
| // } |
| // void ElementsBeingRemoved(iterator first, iterator last) { |
| // /* ... */ |
| // } |
| // void ElementsBeingReplaced() { /* ... */ } |
| // void ElementsWereReplaced() { /* ... */ } |
| // |
| // 5. Optional: implement operator=(const MyProxy&) and operator=(MyProxy&&) |
| // (base class' `operator=` works only with `ContainerType`): |
| // |
| // public: |
| // MyProxy& operator=(const MyProxy& other) { |
| // *this = other.underlying_container(); |
| // return *this; |
| // } |
| // MyProxy& operator=(MyProxy&& other) { |
| // // You probably want to notify `other` about invalidation |
| // // of its **whole container** (not just elements). |
| // *this = std::move(other.underlying_container()); |
| // return *this; |
| // } |
| // |
| // TODO(mglb): The class name is not really conveying what the class does. |
| // Find a better one. More details: |
| // https://github.com/chipsalliance/verible/pull/1244#discussion_r821964959 |
| template <class DerivedType, class ContainerType> |
| class ContainerProxyBase { |
| static_assert(!std::is_const_v<ContainerType>); |
| static_assert(!std::is_reference_v<ContainerType>); |
| static_assert(!std::is_pointer_v<ContainerType>); |
| |
| public: |
| ContainerProxyBase() = default; |
| |
| // Copy/move of the base class doesn't make sense. |
| ContainerProxyBase(const ContainerProxyBase&) = delete; |
| ContainerProxyBase(ContainerProxyBase&&) = delete; |
| ContainerProxyBase& operator=(const ContainerProxyBase&) = delete; |
| ContainerProxyBase& operator=(ContainerProxyBase&&) = delete; |
| |
| using container_type = ContainerType; |
| |
| using value_type = typename ContainerType::value_type; |
| using reference = typename ContainerType::reference; |
| using const_reference = typename ContainerType::const_reference; |
| using iterator = typename ContainerType::iterator; |
| using const_iterator = typename ContainerType::const_iterator; |
| using difference_type = typename ContainerType::difference_type; |
| using size_type = typename ContainerType::size_type; |
| |
| using reverse_iterator = typename ContainerType::reverse_iterator; |
| using const_reverse_iterator = typename ContainerType::const_reverse_iterator; |
| |
| // Iteration |
| |
| iterator begin() { return container().begin(); } |
| const_iterator begin() const { return container().begin(); } |
| const_iterator cbegin() const { return container().cbegin(); } |
| |
| iterator end() { return container().end(); } |
| const_iterator end() const { return container().end(); } |
| const_iterator cend() const { return container().cend(); } |
| |
| reverse_iterator rbegin() { return container().rbegin(); } |
| const_reverse_iterator rbegin() const { return container().rbegin(); } |
| const_reverse_iterator crbegin() const { return container().crbegin(); } |
| |
| reverse_iterator rend() { return container().rend(); } |
| const_reverse_iterator rend() const { return container().rend(); } |
| const_reverse_iterator crend() const { return container().crend(); } |
| |
| // Element access |
| |
| reference front() { return container().front(); } |
| const_reference front() const { return container().front(); } |
| |
| reference back() { return container().back(); } |
| const_reference back() const { return container().back(); } |
| |
| reference operator[](size_type index) noexcept { return container()[index]; } |
| const_reference operator[](size_type index) const noexcept { |
| return container()[index]; |
| } |
| |
| reference at(size_type index) { return container().at(index); } |
| const_reference at(size_type index) const { return container().at(index); } |
| |
| // Modifiers (inserting) |
| |
| // TODO(mglb): provide methods taking `iterator` instead of `const_iterator`. |
| // In most cases `iterator` is convertible to `const_iterator` without any |
| // cost, but that is not guaranteed. |
| |
| template <typename... Args> |
| iterator emplace(const_iterator pos, Args&&... args) { |
| const auto iter = container().emplace(pos, std::forward<Args>(args)...); |
| CallElementsInserted(iter); |
| return iter; |
| } |
| |
| template <typename... Args> |
| void emplace_front(Args&&... args) { |
| container().emplace_front(std::forward<Args>(args)...); |
| CallElementsInserted(container().begin()); |
| } |
| |
| template <typename... Args> |
| void emplace_back(Args&&... args) { |
| container().emplace_back(std::forward<Args>(args)...); |
| CallElementsInserted(std::prev(container().end())); |
| } |
| |
| iterator insert(const_iterator pos, const value_type& value) { |
| const auto iter = container().insert(pos, value); |
| CallElementsInserted(iter); |
| return iter; |
| } |
| |
| iterator insert(const_iterator pos, size_type count, |
| const value_type& value) { |
| const auto iter = container().insert(pos, count, value); |
| CallElementsInserted(iter, std::next(iter, count)); |
| return iter; |
| } |
| |
| iterator insert(const_iterator pos, value_type&& value) { |
| const auto iter = container().insert(pos, std::move(value)); |
| CallElementsInserted(iter); |
| return iter; |
| } |
| |
| template <typename InputIterator> |
| iterator insert(const_iterator pos, InputIterator first, InputIterator last) { |
| const auto iter = container().insert(pos, first, last); |
| const auto end_iter = std::next(iter, std::distance(first, last)); |
| CallElementsInserted(iter, end_iter); |
| return iter; |
| } |
| |
| iterator insert(const_iterator pos, |
| std::initializer_list<value_type> values) { |
| const auto iter = container().insert(pos, values); |
| const auto end_iter = std::next(iter, values.size()); |
| CallElementsInserted(iter, end_iter); |
| return iter; |
| } |
| |
| void push_front(const value_type& value) { |
| container().push_front(value); |
| CallElementsInserted(container().begin()); |
| } |
| void push_front(value_type&& value) { |
| container().push_front(std::move(value)); |
| CallElementsInserted(container().begin()); |
| } |
| |
| void push_back(const value_type& value) { |
| container().push_back(value); |
| CallElementsInserted(std::prev(container().end())); |
| } |
| void push_back(value_type&& value) { |
| container().push_back(std::move(value)); |
| CallElementsInserted(std::prev(container().end())); |
| } |
| |
| // Modifiers (removing) |
| |
| iterator erase(iterator pos) { |
| CallElementsBeingRemoved(pos); |
| return container().erase(pos); |
| } |
| |
| iterator erase(const_iterator pos) { |
| CallElementsBeingRemoved(ConvertToMutableIterator(pos)); |
| return container().erase(pos); |
| } |
| |
| iterator erase(iterator first, iterator last) { |
| CallElementsBeingRemoved(first, last); |
| return container().erase(first, last); |
| } |
| |
| iterator erase(const_iterator first, const_iterator last) { |
| CallElementsBeingRemoved(ConvertToMutableIterator(first), |
| ConvertToMutableIterator(last)); |
| return container().erase(first, last); |
| } |
| |
| void pop_front() { |
| CallElementsBeingRemoved(container().begin()); |
| container().pop_front(); |
| } |
| |
| void pop_back() { |
| CallElementsBeingRemoved(std::prev(container().end())); |
| container().pop_back(); |
| } |
| |
| void clear() { |
| CallElementsBeingRemoved(begin(), end()); |
| container().clear(); |
| } |
| |
| // Assignment |
| |
| template <typename InputIterator> |
| void assign(InputIterator first, InputIterator last) { |
| CallElementsBeingReplaced(); |
| container().assign(first, last); |
| CallElementsWereReplaced(); |
| } |
| |
| void assign(std::initializer_list<value_type> values) { |
| CallElementsBeingReplaced(); |
| container().assign(values); |
| CallElementsWereReplaced(); |
| } |
| |
| void assign(size_type count, const value_type& value) { |
| CallElementsBeingReplaced(); |
| container().assign(count, value); |
| CallElementsWereReplaced(); |
| } |
| |
| // Intended to be exposed via `using` in `DerivedType`. |
| // NOLINTNEXTLINE(misc-unconventional-assign-operator) |
| DerivedType& operator=(const ContainerType& other_container) { |
| CallElementsBeingReplaced(); |
| container() = other_container; |
| CallElementsWereReplaced(); |
| return *derived(); |
| } |
| |
| // Intended to be exposed via `using` in `DerivedType`. |
| // NOLINTNEXTLINE(misc-unconventional-assign-operator) |
| DerivedType& operator=(ContainerType&& other_container) noexcept { |
| CallElementsBeingReplaced(); |
| container() = std::move(other_container); |
| CallElementsWereReplaced(); |
| return *derived(); |
| } |
| |
| // Intended to be exposed via `using` in `DerivedType`. |
| // NOLINTNEXTLINE(misc-unconventional-assign-operator) |
| DerivedType& operator=(std::initializer_list<value_type> values) { |
| CallElementsBeingReplaced(); |
| container() = values; |
| CallElementsWereReplaced(); |
| return *derived(); |
| } |
| |
| void swap(ContainerType& other) { |
| CallElementsBeingReplaced(); |
| container().swap(other); |
| CallElementsWereReplaced(); |
| } |
| |
| void swap(DerivedType& other) { |
| CallElementsBeingReplaced(); |
| other.CallElementsBeingReplaced(); |
| container().swap(other.container()); |
| CallElementsWereReplaced(); |
| other.CallElementsWereReplaced(); |
| } |
| |
| // Standalone `swap` function available only if `DerivedType` implements |
| // `swap(DerivedType&)` method (either through importing this class' method |
| // via `using` or implementing their own version). |
| template <class DT_ = DerivedType, |
| class = decltype(std::declval<DT_>().swap(std::declval<DT_&>()))> |
| friend void swap(DerivedType& a, DerivedType& b) { |
| a.swap(b); |
| } |
| |
| // Capacity |
| |
| size_type size() const { return container().size(); } |
| size_type max_size() const { return container().max_size(); } |
| bool empty() const { return container().empty(); } |
| |
| size_type capacity() const { return container().capacity(); } |
| |
| void reserve(size_type count) { container().reserve(count); } |
| |
| void resize(size_type count) { |
| const auto initial_size = container().size(); |
| if (count < initial_size) { |
| const iterator first_removed = std::next(container().begin(), count); |
| CallElementsBeingRemoved(first_removed, container().end()); |
| } |
| container().resize(count); |
| if (count > initial_size) { |
| const iterator first_inserted = |
| std::next(container().begin(), initial_size); |
| CallElementsInserted(first_inserted, container().end()); |
| } |
| } |
| |
| void resize(size_type count, const value_type& value) { |
| const auto initial_size = container().size(); |
| if (count < initial_size) { |
| const iterator first_removed = std::next(container().begin(), count); |
| CallElementsBeingRemoved(first_removed, container().end()); |
| } |
| container().resize(count, value); |
| if (count > initial_size) { |
| const iterator first_inserted = |
| std::next(container().begin(), initial_size); |
| CallElementsInserted(first_inserted, container().end()); |
| } |
| } |
| |
| protected: |
| // Derived class interface |
| |
| // Mandatory: |
| // ContainerType& underlying_container(); |
| // const ContainerType& underlying_container() const; |
| |
| // Optional: |
| void ElementsInserted(iterator first, iterator last) {} |
| void ElementsBeingRemoved(iterator first, iterator last) {} |
| void ElementsBeingReplaced() {} |
| void ElementsWereReplaced() {} |
| |
| private: |
| iterator ConvertToMutableIterator(const_iterator iter) { |
| return std::next(container().begin(), |
| std::distance(container().cbegin(), iter)); |
| } |
| |
| auto* derived() { return static_cast<DerivedType*>(this); } |
| const auto* derived() const { return static_cast<const DerivedType*>(this); } |
| |
| ContainerType& container() { return derived()->underlying_container(); } |
| const ContainerType& container() const { |
| return derived()->underlying_container(); |
| } |
| |
| void CallElementsInserted(iterator element) { |
| derived()->ElementsInserted(element, std::next(element)); |
| } |
| void CallElementsInserted(iterator first, iterator last) { |
| derived()->ElementsInserted(first, last); |
| } |
| void CallElementsBeingRemoved(iterator element) { |
| derived()->ElementsBeingRemoved(element, std::next(element)); |
| } |
| void CallElementsBeingRemoved(iterator first, iterator last) { |
| derived()->ElementsBeingRemoved(first, last); |
| } |
| void CallElementsBeingReplaced() { derived()->ElementsBeingReplaced(); } |
| void CallElementsWereReplaced() { derived()->ElementsWereReplaced(); } |
| }; |
| |
| // Macros for importing common sets of members in classes deriving from |
| // ContainerProxyBase. |
| // A class deriving from ContainerProxyBase can optionally use **one** |
| // `USING_CONTAINER_PROXY_*_MEMBERS` macro. |
| |
| // Imports (via `using`) members required by C++ Container. |
| // Can be called only inside body of a class deriving from ContainerProxyBase. |
| // `base_type` must be inherited ContainerProxyBase class type. |
| #define USING_CONTAINER_PROXY_CONTAINER_MEMBERS(base_type) \ |
| using typename base_type::value_type; \ |
| using typename base_type::reference; \ |
| using typename base_type::const_reference; \ |
| using typename base_type::iterator; \ |
| using typename base_type::const_iterator; \ |
| using typename base_type::difference_type; \ |
| using typename base_type::size_type; \ |
| using base_type::begin; \ |
| using base_type::end; \ |
| using base_type::cbegin; \ |
| using base_type::cend; \ |
| using base_type::size; \ |
| using base_type::max_size; \ |
| using base_type::empty; \ |
| using base_type::operator=; \ |
| using base_type::swap; |
| |
| // **INTERNAL, DO NOT USE DIRECTLY IN CLASSES!** |
| // Imports (via `using`) extra members required by C++ Reversible Container. |
| // `base_type` must be inherited ContainerProxyBase class type. |
| #define INTERNAL_USING_CONTAINER_PROXY_REVERSIBLE_CONTAINER_MEMBERS(base_type) \ |
| using typename base_type::reverse_iterator; \ |
| using typename base_type::const_reverse_iterator; \ |
| using base_type::rbegin; \ |
| using base_type::rend; \ |
| using base_type::crbegin; \ |
| using base_type::crend; |
| |
| // Imports (via `using`) additional members required by C++ Sequence Container. |
| // Can be called only inside body of a class deriving from ContainerProxyBase. |
| // `base_type` must be inherited ContainerProxyBase class type. |
| #define USING_CONTAINER_PROXY_SEQUENCE_CONTAINER_MEMBERS(base_type) \ |
| USING_CONTAINER_PROXY_CONTAINER_MEMBERS(base_type) \ |
| using base_type::emplace; \ |
| using base_type::insert; \ |
| using base_type::erase; \ |
| using base_type::clear; \ |
| using base_type::assign; \ |
| using base_type::front; |
| |
| // Imports (via `using`) members supported by std::vector. |
| // Can be called only inside body of a class deriving from ContainerProxyBase. |
| // `base_type` must be inherited ContainerProxyBase class type. |
| #define USING_CONTAINER_PROXY_STD_VECTOR_MEMBERS(base_type) \ |
| USING_CONTAINER_PROXY_SEQUENCE_CONTAINER_MEMBERS(base_type) \ |
| INTERNAL_USING_CONTAINER_PROXY_REVERSIBLE_CONTAINER_MEMBERS(base_type) \ |
| using base_type::back; \ |
| using base_type::emplace_front; \ |
| using base_type::emplace_back; \ |
| using base_type::push_front; \ |
| using base_type::push_back; \ |
| using base_type::pop_front; \ |
| using base_type::pop_back; \ |
| using base_type::operator[]; \ |
| using base_type::at; \ |
| using base_type::capacity; \ |
| using base_type::reserve; \ |
| using base_type::resize; |
| |
| // Imports (via `using`) members supported by std::list. |
| // Can be called only inside body of a class deriving from ContainerProxyBase. |
| // `base_type` must be inherited ContainerProxyBase class type. |
| #define USING_CONTAINER_PROXY_STD_LIST_MEMBERS(base_type) \ |
| USING_CONTAINER_PROXY_SEQUENCE_CONTAINER_MEMBERS(base_type) \ |
| INTERNAL_USING_CONTAINER_PROXY_REVERSIBLE_CONTAINER_MEMBERS(base_type) \ |
| using base_type::back; \ |
| using base_type::emplace_front; \ |
| using base_type::emplace_back; \ |
| using base_type::push_front; \ |
| using base_type::push_back; \ |
| using base_type::pop_front; \ |
| using base_type::pop_back; \ |
| using base_type::resize; |
| |
| } // namespace verible |
| |
| #endif // VERIBLE_COMMON_UTIL_CONTAINER_PROXY_H_ |