#ifndef FDA_H_INCLUDED #define FDA_H_INCLUDED #include "kblib/kblib.h" #include #include #include #include #include #include #include namespace wg_containers { /* class falldown_array & helpers. Purpose: falldown_array implements the particular overloading strategy used for template-node-names in WordGen/cpp2. In that system, a node has a declared number of arguments, and can also be variadic, which means it will accept any number of extra arguments. Overload resolution proceeds as follows: - Given a reference to node N with K arguments: - If a node N_K exists (which has exactly K arguments, variadically or not), select that node. - Otherwise: - If not NV_Shadow_V: Search for any node N_vJ, where J is the largest positive number less than K where a variadic node is found. Return N_vJ if it exists. - If NV_Shadow_V: Search for any node N_J, where J is the largest positive number less than K where a node is found, even if it is not variadic. - If N_J is variadic, the search is successful, and N_J is returned. - Else, the search is unsuccessful. - On failure, either null is returned or an exception is thrown, depending on the interface used. Insertion requires an index and a boolean. Lookup uses only the index. This assymmetry, as well as the lookup strategy, which can find a non-exact match, mean that this container is not strictly a sequence or associative container. Note: falldown_array satisfies ReversibleContainer and AllocatorAwareContainer, but not SequenceContainer or AssociativeContainer. If the DenyIteration policy is set, falldown_array does not satisfy Container at all, but most of the interface still works, and there (should) be marginal gains in efficiency. Policy rationale: There are two policies that can be specified orthogonally; both default to false. DenyIteration: disables the iterator part of the interface, and removes all unnecessary bookkeeping data. In theory, this improves performance, but I have not tested for that. NV_Shadow_V: stands for "nonvariadic shadows variadic". If set, the container implements the mathematically simpler lookup strategy of always checking the closest element to the requested index in the downward direction, and it will fail to look up if that node is non-variadic, ignoring anything further down. This means that there is less bookkeeping to do on insertion, and should speed it up slightly. Again, this performance claim is untested. Complexity: Insertion: Worst-case linear in size of container. Lookup: Constant. Iteration: Increment and decrement are constant time. */ template class el_impl { public: T val; bool variadic; std::ptrdiff_t next; }; template class el_impl { public: template el_impl(Ti&& v, bool var, [[maybe_unused]] std::ptrdiff_t) : val(v), variadic(var) {} T val; bool variadic; }; struct tombstone_t { // The tombstone is not templated because this bookkeeping // information does not increase the size of the container. // Distance to the last element, for iteration. std::ptrdiff_t dist{}; }; // using backref_t = std::ptrdiff_t; struct backref_t { std::ptrdiff_t dist_var; std::ptrdiff_t dist_nvar; }; template using element = std::variant, backref_t>; enum class fda_policy : unsigned { Default = 0, NV_Shadow_V = 1, DenyIteration = 2, }; [[nodiscard]] constexpr fda_policy operator|(fda_policy l, fda_policy r) { using kblib::etoi; return static_cast(etoi(l) | etoi(r)); } [[nodiscard]] constexpr fda_policy operator&(fda_policy l, fda_policy r) { using kblib::etoi; return static_cast(etoi(l) & etoi(r)); } template class f_iterator { private: using live_type = el_impl, true>; public: using difference_type = std::ptrdiff_t; using value_type = T; using pointer = T*; using reference = T&; using iterator_category = std::bidirectional_iterator_tag; f_iterator() = default; explicit f_iterator(BI p) noexcept : _p(p) {} [[nodiscard]] reference operator*() {return std::get(*_p).val;} [[nodiscard]] pointer operator->() {return &std::get(*_p).val;} f_iterator& operator++() { _p += std::get(*_p).next; return *this; } f_iterator& operator++(int) { auto tmp = *this; operator++(); return tmp; } f_iterator& operator--() { --_p; _p -= std::visit(kblib::visitor{ [](tombstone_t t) {return t.dist;}, [](const live_type&) {return 0;}, [](backref_t d) {return d.dist_nvar;} }, *_p); return *this; } f_iterator& operator--(int) { auto tmp = *this; operator--(); return tmp; } template friend void swap(f_iterator& a, f_iterator& b) noexcept { std::swap(a._p, b._p); } template friend bool operator==(const f_iterator a, const f_iterator b) noexcept; template friend bool operator<(const f_iterator a, const f_iterator b) noexcept; [[nodiscard]] auto get() const noexcept { return &const_cast&>(*_p); } private: template friend class falldown_array; BI _p; }; inline namespace comp { template [[nodiscard]] bool operator==(const f_iterator a, const f_iterator b) noexcept { return a._p == b._p; } template [[nodiscard]] bool operator!=(const f_iterator a, const f_iterator b) noexcept { return !(a == b); } template [[nodiscard]] bool operator<(const f_iterator a, const f_iterator b) noexcept { return a._p < b._p; } template [[nodiscard]] bool operator>(const f_iterator a, const f_iterator b) noexcept { return b < a; } template [[nodiscard]] bool operator>=(const f_iterator a, const f_iterator b) noexcept { return !(a < b); } template [[nodiscard]] bool operator<=(const f_iterator a, const f_iterator b) noexcept { return !(b < a); } } template struct info_t { P ptr; std::ptrdiff_t idx; bool variadic; operator bool() const noexcept {return ptr;} }; template std::ostream& operator<<(std::ostream& os, const info_t

& i) { return os<<"{ptr: "< class falldown_array { constexpr static bool AllowIteration = (policy & fda_policy::DenyIteration) != fda_policy::DenyIteration; constexpr static bool DoShadow = (policy & fda_policy::NV_Shadow_V) == fda_policy::NV_Shadow_V; public: /* Boilerplate stuff */ using size_type = std::size_t; using index_type = std::ptrdiff_t; using difference_type = index_type; static_assert(std::is_same_v>>); using value_type = T; using reference = value_type&; using const_reference = const T&; private: using pointer = T*; using const_pointer = const T*; using element_type = element; using live_type = el_impl; public: using allocator_type = std::conditional_t< std::is_void_v, std::allocator, Alloc>; private: using BC = std::vector; public: static constexpr index_type npos = std::numeric_limits::max()/2; using iterator = std::conditional_t, void>; using const_iterator = std::conditional_t, void>; using reverse_iterator = std::conditional_t, void>; using const_reverse_iterator = std::conditional_t, void>; falldown_array() = default; falldown_array(const falldown_array&) = default; falldown_array(falldown_array&&) = default; struct el_val { index_type idx; bool var; T val; }; falldown_array(std::initializer_list v) : falldown_array() { assign(v); } falldown_array& operator=(const falldown_array&) = default; falldown_array& operator=(falldown_array&&) = default; falldown_array& operator=(std::initializer_list v) { assign(v); } allocator_type get_allocator() const {return backing.get_allocator();} void assign(std::initializer_list v) { clear(); for (auto& e : v) { set(e.idx, e.var, e.val); } } auto set(index_type idx, bool variadic, const_reference val) noexcept(std::is_nothrow_copy_constructible_v) { return emplace(idx, variadic, val); } template auto emplace(index_type idx, bool variadic, Args&&... args) noexcept(std::is_nothrow_constructible_v) { assert_conditions(); //the offset is 2 because backing is required to hold one additional element after the last live_type if (idx > static_cast(backing.size()) - 2) { const auto start_adjusting = static_cast(backing.size()) - 1; backing.resize(idx+2); if (start_adjusting >= 0) { propagate_ref_end(start_adjusting); } assert_conditions(); } const typename BC::iterator pos = backing.begin()+idx; bool need_adjust = !(std::holds_alternative(*pos) && (std::get(*pos).variadic == variadic)); if (need_adjust) { *pos = live_type{value_type(std::forward(args...)), variadic, 0}; propagate_ref_mid(idx); } else { std::get(*pos).val = value_type(std::forward(args...)); } _first = std::min(_first, idx); _size = std::count_if( backing.begin()+_first, backing.end(), [](const element_type& e) { return std::holds_alternative(e); } ); assert_conditions(); if constexpr (AllowIteration) { return iterator{pos}; } else { return; } } [[nodiscard]] size_type size() const noexcept {return _size;} [[nodiscard]] size_type capacity() const noexcept {return backing.capacity();} [[nodiscard]] size_type empty() const noexcept {return backing.empty();} [[nodiscard]] size_type max_size() const noexcept { return std::min( backing.max_size(), static_cast(npos) - 1 ); } void reserve(size_type s) {backing.reserve(s);} void shrink_to_fit() {backing.shrink_to_fit();} void clear() noexcept { backing = {{tombstone_t{}}}; _first = npos; _size = 0; } const_reference operator[](index_type idx) const noexcept(false) { //throws std::out_of_range on hitting tombstone const auto pos = adjust_down(backing.begin()+std::min( idx, static_cast(backing.size())-1 )); return std::get(*pos).val; } reference operator[](index_type idx) noexcept(false) { //throws std::out_of_range on hitting tombstone const auto pos = adjust_down(backing.begin()+std::min( idx, static_cast(backing.size())-1 )); return std::get(*pos).val; } info_t info(index_type idx) noexcept { const auto pos = adjust_down(backing.begin()+std::min( idx, static_cast(backing.size())-1 )); if (auto p = std::get_if(&*pos)) { return {&p->val, pos - backing.begin(), p->variadic}; } else { return {nullptr, pos - backing.begin(), false}; } } info_t info(index_type idx) const noexcept { const auto pos = adjust_down(backing.begin()+std::min( idx, static_cast(backing.size())-1 )); if (auto p = std::get_if(&*pos)) { return {&p->val, pos - backing.begin(), p->variadic}; } else { return {nullptr, pos - backing.begin(), false}; } } pointer get(index_type idx) noexcept { return info(idx).ptr; } const_pointer get(index_type idx) const noexcept { return info(idx).ptr; } iterator begin() noexcept {return iterator{backing.begin()+_first};} iterator end() noexcept {return iterator{backing.end()};} const_iterator begin() const noexcept {return iterator{backing.begin()+_first};} const_iterator end() const noexcept {return iterator{backing.end()};} reverse_iterator rbegin() noexcept { return std::make_reverse_iterator(iterator{backing.end()}); } reverse_iterator rend() noexcept { return std::make_reverse_iterator(iterator{backing.begin()+_first}); } const_reverse_iterator rbegin() const noexcept { return std::make_reverse_iterator(iterator{backing.end()}); } const_reverse_iterator rend() const noexcept { return std::make_reverse_iterator(iterator{backing.begin()+_first}); } const_iterator cbegin() const noexcept {return const_iterator{backing.begin()+_first};} const_iterator cend() const noexcept {return const_iterator{backing.end()};} const_reverse_iterator crbegin() const noexcept { return std::make_reverse_iterator(const_iterator{backing.end()}); } const_reverse_iterator crend() const noexcept { return std::make_reverse_iterator(const_iterator{backing.begin()+_first}); } // template friend void swap(falldown_array& a, falldown_array& b) { using std::swap; swap(a.backing, b.backing); swap(a._first, b._first); swap(a._size, b._size); } private: //Perform a lookup. //In constant time, jump to the controlling element for the given index. //If there is no such element, or if said element is nonvariadic, then: //if nothrow: return the argument unchanged. //else: throw std::out_of_range. template static typename BC::iterator adjust_down(typename BC::iterator pos) noexcept(nothrow) { return std::visit(kblib::visitor{ [&](tombstone_t) -> typename BC::iterator { if constexpr (nothrow) { return pos; } else { throw std::out_of_range(""); } }, [&](live_type&) {return pos;}, [&](backref_t d) {return pos-d.dist_var;} }, *pos); } //Starting from the previous live value, adjusts all back- and forward-references until it reaches the start position. void propagate_ref_end(index_type start) noexcept { const auto start_pos = backing.begin() + start; const auto end = backing.end(); auto [count, variadic] = scan_backward_for_last_var(start+1); auto dist_nvar = scan_backward_for_last_entry(start+1); for (auto it = start_pos+1; it != end && !std::holds_alternative(*it); ++it) { if (variadic) { *it = backref_t{count, dist_nvar}; } else { *it = tombstone_t{count}; } ++count; ++dist_nvar; } } //Repairs the structure of the falldown_array after a middle-insert. This entails scanning forward to set backreferences, and backward to set next indices. void propagate_ref_mid(index_type start) noexcept { const typename BC::iterator pos = backing.begin()+start, begin = backing.begin(), end = backing.end(); assert(start < backing.size()); assert(std::holds_alternative(*pos)); auto& reference = std::get(*pos); propagate_ref_end(start); if constexpr (AllowIteration) { reference.next = scan_forward_for_next_entry(start+1)-start; if (reference.next + start == backing.size()) { reference.next = npos; } auto last = scan_backward_for_last_entry(start-1); if (last != start) { std::get(backing[last]).next = (start - last); } } return; } //Perform a partial relative backward lookup: //If DoShadow, find the nearest live element and return the distance as well as if it is variadic. //Else, find the nearest variadic live element and return the distance and true. //Else, if there is no nearest variadic live element, return the distance to the nearest nonvariadic live element, and false. //Else, the search reached the beginning. Return the distance to the beginning, and false. std::pair scan_backward_for_last_var(index_type start) const noexcept { const typename BC::const_iterator pos = backing.begin()+start, begin = backing.begin(); std::optional nvar_pos; if (pos != begin) { auto it = pos - 1; difference_type steps = 0; do { ++steps; if (auto p = std::get_if(&*it)) { if constexpr (DoShadow) { return {steps, p->variadic}; } else { if (p->variadic) { return {steps, true}; } else if (!nvar_pos) { nvar_pos.emplace(steps); } } } else if (auto p = std::get_if(&*it)) { return {steps + p->dist_var, true}; } else if (auto p = std::get_if(&*it)) { if (nvar_pos) { return {*nvar_pos, false}; } return {steps + p->dist, false}; } } while (--it != begin); if (nvar_pos) { return {*nvar_pos, false}; } return {start, false}; } else { return {0, false}; } } //Return the index of the next live value, starting at start. index_type scan_forward_for_next_entry(index_type start) const noexcept { return kblib::find_in_if( backing.begin()+start, backing.end(), [](const element_type& el) { return std::holds_alternative(el); })+start; } //Return the index of the previous live value, starting at start. index_type scan_backward_for_last_entry(index_type start) const noexcept { //Naive search: return kblib::find_last_in_if( backing.begin(), backing.begin()+start+1, [](const element_type& el) { return std::holds_alternative(el); }); //Assumes all preconditions are valid: // const typename BC::const_iterator // begin = backing.begin(); // typename BC::const_iterator // pos = begin+start; // for (index_type steps{}; pos != begin; ++steps,--pos) { // if (std::holds_alternative(*pos)) { // return steps; // } else if (auto p = std::get_if(&*pos)) { // return steps + p->dist_var; // } else if (auto p = std::get_if(&*pos)) { // return steps + p->dist; // } // } // return start; } void assert_conditions() noexcept { using namespace std::literals; std::cerr<<"--------\n"; if (!backing.empty()) { { bool var{false}; index_type counter{}; index_type toNext{_first}; [[maybe_unused]] index_type idx = 0; for (const auto& el : backing) { std::cerr<(backing.back())); } return; //all good } BC backing {tombstone_t{}}; index_type _first{npos}; size_type _size{}; }; } void c_test_func(); #endif //FDA_H_INCLUDED