/* ***************************************************************************** * kblib is a general utility library for C++14 and C++17, intended to provide * performant high-level abstractions and more expressive ways to do simple * things. * * Copyright (c) 2021 killerbee * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ****************************************************************************/ /** * @file * @brief Provides generic operations for containers, as well as kblib::stack. * * @author killerbee * @date 2019-2021 * @copyright GNU General Public Licence v3.0 */ #ifndef KBLIB_CONTAINERS_H #define KBLIB_CONTAINERS_H #include "fakestd.h" #include "iterators.h" #include "tdecl.h" #include "traits.h" #include #include #include #include #include #include #include namespace KBLIB_NS { template KBLIB_NODISCARD constexpr auto pop(C& s) -> typename C::value_type { typename C::value_type ret = std::move(s.top()); s.pop(); return ret; } template KBLIB_NODISCARD constexpr auto get_or(const C& m, const K& key, const V& defval) -> typename C::mapped_type { auto it = m.find(key); if (it == m.end()) return defval; else return it->second; } template KBLIB_NODISCARD constexpr auto try_get(Map& map, Key&& key) -> copy_const_t* { auto it = map.find(std::forward(key)); if (it == map.end()) return nullptr; else return &it->second; } template struct exists_t { iterator it; bool found; KBLIB_NODISCARD constexpr operator bool() const noexcept { return found; } KBLIB_NODISCARD constexpr auto operator*() const noexcept(noexcept(*it)) -> decltype(*it) { return *it; } KBLIB_NODISCARD constexpr auto operator->() const noexcept -> iterator { return it; } KBLIB_NODISCARD constexpr auto addr() const noexcept -> auto* { return to_pointer(it); } }; template KBLIB_NODISCARD constexpr auto get_check(M&& m, const K& key) noexcept( noexcept(m.find(key) != m.end())) -> exists_t { auto it = m.find(key); return {it, it != m.end()}; } /** * @brief std::vector::shrink_to_fit is non-binding, which means that there is * no guaranteed way to shrink a vector via its API. This function is a * roundabout way of doing that without relying on the sanity of the * implementation (except that it assumes that a vector won't significantly * over-allocate on sized construction). * * This function explicitly constructs a new vector and moves into it, before * overwriting the old vector with the new one, meaning that the vector is * forced to forget its capacity. * * This function provides the strong exception guarantee. * * @param vec The vector to force-shrink. */ template auto force_shrink_to_fit(V& vec) -> void { if (std::is_nothrow_move_constructible::value) { V tmp; try_reserve(tmp, vec.size()); std::move(vec.begin(), vec.end(), std::back_inserter(tmp)); vec = std::move(tmp); } else { V tmp(vec.begin(), vec.end()); vec = std::move(tmp); } return; } template struct construct_with_size { KBLIB_NODISCARD constexpr static auto make() -> C { return C(size); } }; template struct construct_with_capacity { KBLIB_NODISCARD constexpr static auto make() -> C { C c; c.reserve(size); return c; } }; /** * @brief Allows for constructing a container of a specified type from a range * object. Copy elision means that this does not result in any extra copies. * * @tparam Container The container type to construct an object of. * @param r The range to construct the returned container from. * @return Container Container{begin(r), end(r)}; */ template KBLIB_NODISCARD constexpr auto construct_from_range(Range&& r) -> Container { using std::begin; using std::end; return Container{begin(std::forward(r)), end(std::forward(r))}; } template > class KBLIB_NODISCARD build_iterator { private: /** * @brief range A shared_ptr to the managed range. * * It is unfortunate that this has to be a shared_ptr, but that's the only * way to make this class a valid iterator. A move-only build_iterator-alike * could avoid this overhead, and I may write one because several algorithms * don't ever need to copy iterators. */ std::shared_ptr range; public: using value_type = void; using difference_type = void; using pointer = void; using reference = void; using iterator_category = std::output_iterator_tag; template build_iterator(Args&&... args) : range(std::make_shared(std::forward(args)...)) {} KBLIB_NODISCARD constexpr auto base() noexcept( std::is_nothrow_move_constructible::value) -> Container { auto holder = std::move(range); return std::move(*holder); } KBLIB_NODISCARD constexpr explicit operator Container() noexcept( std::is_nothrow_move_constructible::value) { auto holder = std::move(range); return std::move(*holder); } /** * @brief Creates a temporary std::back_insert_iterator for the range and * returns it. * * Returning an iterator from operator* might look strange, but * std::back_insert_iterator can be assigned to to insert into the range, * and its operator* returns itself anyhow. */ KBLIB_NODISCARD constexpr auto operator*() const noexcept(noexcept(*std::back_inserter(*range))) -> decltype(auto) { return std::back_inserter(*range); } /** * @brief A no-op. */ KBLIB_NODISCARD constexpr auto operator++() -> build_iterator& { return *this; } /** * @brief A no-op. */ KBLIB_NODISCARD constexpr auto operator++(int) -> build_iterator& { return *this; } }; KBLIB_UNUSED constexpr struct build_end_t { template KBLIB_NODISCARD constexpr operator T() const noexcept(noexcept(T{std::declval()})) { return T{this}; } } build_end; template class KBLIB_NODISCARD build_iterator { public: using value_type = void; using difference_type = void; using pointer = void; using reference = void; using iterator_category = std::output_iterator_tag; template build_iterator(Args&&... args) : range(std::make_shared(std::forward(args)...)) {} build_iterator(const build_end_t&) : range{nullptr} , index(std::tuple_size::value) {} KBLIB_NODISCARD auto base() noexcept( std::is_nothrow_move_constructible::value) -> Container { auto holder = std::move(range); return std::move(*holder); } KBLIB_NODISCARD explicit operator Container() noexcept( std::is_nothrow_move_constructible::value) { auto holder = std::move(range); return std::move(*holder); } KBLIB_NODISCARD auto operator*() const noexcept -> decltype(auto) { return (*range)[index]; } KBLIB_NODISCARD auto operator->() const noexcept -> auto* { return &(*range)[index]; } /** * @brief Advance to the next element. */ auto operator++() -> build_iterator& { ++index; return *this; } /** * @brief Advance to the next element. */ auto operator++(int) -> build_iterator& { auto tmp = *this; ++index; return tmp; } KBLIB_NODISCARD constexpr auto size() const noexcept -> std::size_t { return kblib::size(*range); } KBLIB_NODISCARD constexpr friend auto operator==( const build_iterator& it, build_end_t) noexcept -> bool { return it.index == it.size(); } KBLIB_NODISCARD constexpr friend auto operator!=( const build_iterator& it, build_end_t) noexcept -> bool { return it.index != it.size(); } KBLIB_NODISCARD constexpr friend auto operator==( build_end_t, const build_iterator& it) noexcept -> bool { return it.index == it.size(); } KBLIB_NODISCARD constexpr friend auto operator!=( build_end_t, const build_iterator& it) noexcept -> bool { return it.index != it.size(); } KBLIB_NODISCARD constexpr friend auto operator==( const build_iterator& it1, const build_iterator& it2) noexcept -> bool { return it1.index == it2.index; } KBLIB_NODISCARD constexpr friend auto operator!=( const build_iterator& it1, const build_iterator& it2) noexcept -> bool { return it1.index != it2.index; } private: /** * @brief range A shared_ptr to the managed range. * * It is unfortunate that this has to be a shared_ptr, but that's the only * way to make this class a valid iterator. A move-only build_iterator-alike * could avoid this overhead, and I may write one because several algorithms * don't ever need to copy iterators. */ std::shared_ptr range; std::size_t index{}; }; } // namespace KBLIB_NS namespace std { #if defined(__clang__) // Because apparently -Wno-mismatched-tags doesn't work # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wmismatched-tags" #endif template struct tuple_size<::kblib::construct_with_size> : public integral_constant {}; #if defined(__clang__) # pragma clang diagnostic pop #endif } // namespace std namespace KBLIB_NS { namespace detail { template struct buildiota_impl, false> { template constexpr static auto impl(T value) -> Container { Container out = construct_with_size::make(); for (auto& v : out) { v = value; ++value; } return out; } template constexpr static auto impl(T value, I incr) -> Container { Container out = construct_with_size::make(); for (auto& v : out) { v = value; value += incr; } return out; } }; } // namespace detail template > class [[deprecated("use a class derived from std::stack instead")]] stack { public: // Member types using container_type = Container; using value_type = typename Container::value_type; using size_type = typename Container::size_type; using reference = typename Container::reference; using const_reference = typename Container::const_reference; static_assert(std::is_same::value, "Container::value_type must be T."); private: container_type backing; public: // Constructors stack() : stack(Container()) {} explicit stack(const Container& cont) : backing(cont) {} template ::value, int>::type = 0> explicit stack(const Alloc& alloc); template ::value, int>::type = 0> stack(const Container& cont, const Alloc& alloc); template ::value, int>::type = 0> stack(Container && cont, const Alloc& alloc); template ::value, int>::type = 0> stack(const stack& cont, const Alloc& alloc); template ::value, int>::type = 0> stack(stack && cont, const Alloc& alloc); // Element access KBLIB_NODISCARD auto top()& noexcept(noexcept(backing.back()))->reference { return backing.back(); } KBLIB_NODISCARD auto top() const& noexcept(noexcept(backing.back())) ->const_reference { return backing.back(); } // Capacity KBLIB_NODISCARD auto empty() const noexcept->bool { return backing.empty(); } KBLIB_NODISCARD auto size() const noexcept->size_type { return backing.size(); } // Modifiers auto push(const value_type& value)->decltype(auto) { return backing.push_back(value); } auto push(value_type && value)->decltype(auto) { return backing.push_back(std::move(value)); } template auto emplace(Args && ... args)&->decltype(auto) { return backing.emplace_back(std::forward(args)...); } auto pop() noexcept(noexcept(backing.pop_back()))->void { backing.pop_back(); return; } auto clear() noexcept(noexcept(backing.clear()))->void { backing.clear(); return; } auto swap(stack & other) noexcept(fakestd::is_nothrow_swappable::value) ->void { using std::swap; swap(backing, other.backing); return; } // Container access KBLIB_NODISCARD auto container() const&->container_type& { return backing; } KBLIB_NODISCARD auto container()&->container_type& { return backing; } KBLIB_NODISCARD auto container()&&->container_type { return std::move(backing); } }; } // namespace KBLIB_NS #endif // KBLIB_CONTAINERS_H