My Project
fda.h
1 #ifndef FDA_H_INCLUDED
2 #define FDA_H_INCLUDED
3 
4 #include "kblib/kblib.h"
5 
6 #include <iterator>
7 #include <optional>
8 #include <type_traits>
9 #include <utility>
10 #include <variant>
11 #include <vector>
12 
13 #include <iostream>
14 
15 namespace wg_containers {
16 
17 /*
18 class falldown_array & helpers.
19 
20 Purpose: falldown_array implements the particular overloading
21  strategy used for template-node-names in WordGen/cpp2. In
22  that system, a node has a declared number of arguments, and
23  can also be variadic, which means it will accept any number
24  of extra arguments. Overload resolution proceeds as follows:
25  - Given a reference to node N with K arguments:
26  - If a node N_K exists (which has exactly K arguments,
27  variadically or not), select that node.
28  - Otherwise:
29  - If not NV_Shadow_V: Search for any node N_vJ, where J is the
30  largest positive number less than K where a variadic node
31  is found. Return N_vJ if it exists.
32  - If NV_Shadow_V: Search for any node N_J, where J is the
33  largest positive number less than K where a node is found,
34  even if it is not variadic.
35  - If N_J is variadic, the search is successful, and N_J is
36  returned.
37  - Else, the search is unsuccessful.
38  - On failure, either null is returned or an exception is
39  thrown, depending on the interface used.
40 
41  Insertion requires an index and a boolean. Lookup uses only
42  the index. This assymmetry, as well as the lookup strategy,
43  which can find a non-exact match, mean that this container is
44  not strictly a sequence or associative container.
45 
46 Note: falldown_array satisfies ReversibleContainer and
47  AllocatorAwareContainer, but not SequenceContainer or
48  AssociativeContainer. If the DenyIteration policy is set,
49  falldown_array does not satisfy Container at all, but most
50  of the interface still works, and there (should) be marginal
51  gains in efficiency.
52 
53 Policy rationale: There are two policies that can be specified
54  orthogonally; both default to false.
55  DenyIteration: disables the iterator part of the interface, and
56  removes all unnecessary bookkeeping data. In theory, this
57  improves performance, but I have not tested for that.
58  NV_Shadow_V: stands for "nonvariadic shadows variadic". If set,
59  the container implements the mathematically simpler lookup
60  strategy of always checking the closest element to the
61  requested index in the downward direction, and it will fail
62  to look up if that node is non-variadic, ignoring anything
63  further down. This means that there is less bookkeeping to
64  do on insertion, and should speed it up slightly. Again,
65  this performance claim is untested.
66 
67 Complexity:
68  Insertion: Worst-case linear in size of container.
69  Lookup: Constant.
70  Iteration: Increment and decrement are constant time.
71 
72 */
73 
74 template <typename T, bool SaveNext>
75 class el_impl {
76  public:
77  T val;
78  bool variadic;
79  std::ptrdiff_t next;
80 };
81 template <typename T>
82 class el_impl<T, false> {
83  public:
84  template <typename Ti>
85  el_impl(Ti&& v, bool var, [[maybe_unused]] std::ptrdiff_t _)
86  : val(std::forward(v)), variadic(var) {}
87  T val;
88  bool variadic;
89 };
90 
91 struct tombstone_t {
92  // The tombstone is not templated because this bookkeeping
93  // information does not increase the size of the container.
94  // Distance to the last element, for iteration.
95  std::ptrdiff_t dist{};
96 };
97 
98 // using backref_t = std::ptrdiff_t;
99 struct backref_t {
100  std::ptrdiff_t dist_var;
101  std::ptrdiff_t dist_nvar;
102 };
103 
104 template <typename T, bool I>
105 using element = std::variant<tombstone_t, el_impl<T, I>, backref_t>;
106 
107 enum class fda_policy : unsigned {
108  Default = 0,
109  NV_Shadow_V = 1,
110  DenyIteration = 2,
111 };
112 
113 [[nodiscard]] constexpr fda_policy operator|(fda_policy l,
114  fda_policy r) noexcept {
115  using kblib::etoi;
116  return static_cast<fda_policy>(etoi(l) | etoi(r));
117 }
118 
119 [[nodiscard]] constexpr fda_policy operator&(fda_policy l,
120  fda_policy r) noexcept {
121  using kblib::etoi;
122  return static_cast<fda_policy>(etoi(l) & etoi(r));
123 }
124 
125 template <typename T, typename BI>
126 class f_iterator {
127  private:
129 
130  public:
131  using difference_type = std::ptrdiff_t;
132  using value_type = T;
133  using pointer = T*;
134  using reference = T&;
135  using iterator_category = std::bidirectional_iterator_tag;
136 
137  f_iterator() = default;
138  explicit f_iterator(BI p) noexcept : _p(p) {}
139 
140  [[nodiscard]] reference operator*() { return std::get<live_type>(*_p).val; }
141  [[nodiscard]] pointer operator->() { return &std::get<live_type>(*_p).val; }
142 
143  f_iterator& operator++() {
144  _p += std::get<live_type>(*_p).next;
145  return *this;
146  }
147  const f_iterator operator++(int) {
148  auto tmp = *this;
149  operator++();
150  return tmp;
151  }
152 
153  f_iterator& operator--() {
154  --_p;
155  _p -= kblib::visit2(
156  *_p, [](tombstone_t t) { return t.dist; },
157  [](const live_type&) { return 0; },
158  [](backref_t d) { return d.dist_nvar; });
159  return *this;
160  }
161  const f_iterator operator--(int) {
162  auto tmp = *this;
163  operator--();
164  return tmp;
165  }
166 
167  template <typename T1, typename BI1>
168  friend void swap(f_iterator& a, f_iterator& b) noexcept {
169  std::swap(a._p, b._p);
170  }
171 
172  template <typename T1, typename BI1>
173  friend bool operator==(f_iterator<T1, BI1> a,
174  f_iterator<T1, BI1> b) noexcept;
175  template <typename T1, typename BI1>
176  friend bool operator<(f_iterator<T1, BI1> a, f_iterator<T1, BI1> b) noexcept;
177 
178  [[nodiscard]] auto get() const noexcept {
179  return &static_cast<const std::decay_t<decltype(*_p)>&>(*_p);
180  }
181 
182  private:
183  template <typename T1, fda_policy policy, typename Alloc>
184  friend class falldown_array;
185  BI _p;
186 };
187 
188 inline namespace comp {
189 
190  template <typename T1, typename BI1>
191  [[nodiscard]] bool operator==(const f_iterator<T1, BI1> a,
192  const f_iterator<T1, BI1> b) noexcept {
193  return a._p == b._p;
194  }
195 
196  template <typename T1, typename BI1>
197  [[nodiscard]] bool operator!=(const f_iterator<T1, BI1> a,
198  const f_iterator<T1, BI1> b) noexcept {
199  return !(a == b);
200  }
201 
202  template <typename T1, typename BI1>
203  [[nodiscard]] bool operator<(const f_iterator<T1, BI1> a,
204  const f_iterator<T1, BI1> b) noexcept {
205  return a._p < b._p;
206  }
207 
208  template <typename T1, typename BI1>
209  [[nodiscard]] bool operator>(const f_iterator<T1, BI1> a,
210  const f_iterator<T1, BI1> b) noexcept {
211  return b < a;
212  }
213 
214  template <typename T1, typename BI1>
215  [[nodiscard]] bool operator>=(const f_iterator<T1, BI1> a,
216  const f_iterator<T1, BI1> b) noexcept {
217  return !(a < b);
218  }
219 
220  template <typename T1, typename BI1>
221  [[nodiscard]] bool operator<=(const f_iterator<T1, BI1> a,
222  const f_iterator<T1, BI1> b) noexcept {
223  return !(b < a);
224  }
225 
226 } // namespace comp
227 
228 template <typename P>
229 struct info_t {
230  P ptr;
231  std::ptrdiff_t idx;
232  bool variadic;
233  operator bool() const noexcept { return ptr; }
234 };
235 template <typename P>
236 std::ostream& operator<<(std::ostream& os, const info_t<P>& i) {
237  return os << "{ptr: " << i.ptr << ", idx: " << i.idx
238  << ", variadic: " << i.variadic << "}";
239 }
240 
241 template <typename T, fda_policy policy = fda_policy::Default,
242  typename Alloc = void>
244  constexpr static bool AllowIteration =
245  (policy & fda_policy::DenyIteration) != fda_policy::DenyIteration;
246  constexpr static bool DoShadow =
247  (policy & fda_policy::NV_Shadow_V) == fda_policy::NV_Shadow_V;
248 
249  public:
250  /* Boilerplate stuff */
251  using size_type = std::size_t;
252  using index_type = std::ptrdiff_t;
253  using difference_type = index_type;
254 
255  static_assert(
256  std::is_same_v<T, std::remove_reference_t<std::remove_cv_t<T>>>);
257  using value_type = T;
258  using reference = value_type&;
259  using const_reference = const T&;
260 
261  private:
262  using pointer = T*;
263  using const_pointer = const T*;
264 
265  using element_type = element<value_type, AllowIteration>;
267 
268  public:
269  using allocator_type =
270  std::conditional_t<std::is_void_v<Alloc>, std::allocator<element_type>,
271  Alloc>;
272 
273  private:
274  using BC = std::vector<element_type, allocator_type>;
275 
276  public:
277  static constexpr index_type npos =
278  std::numeric_limits<difference_type>::max() / 2;
279 
280  using iterator =
281  std::conditional_t<AllowIteration,
283  using const_iterator = std::conditional_t<
284  AllowIteration,
286 
287  using reverse_iterator =
288  std::conditional_t<AllowIteration, std::reverse_iterator<iterator>,
289  void>;
290  using const_reverse_iterator =
291  std::conditional_t<AllowIteration, std::reverse_iterator<const_iterator>,
292  void>;
293 
294  falldown_array() = default;
295  falldown_array(const falldown_array&) = default;
296  falldown_array(falldown_array&&) noexcept = default;
297 
298  struct el_val {
299  index_type idx;
300  bool var;
301  T val;
302  };
303  falldown_array(std::initializer_list<el_val> v) : falldown_array() {
304  assign(v);
305  }
306 
307  falldown_array& operator=(const falldown_array&) = default;
308  falldown_array& operator=(falldown_array&&) noexcept = default;
309  falldown_array& operator=(std::initializer_list<el_val> v) { assign(v); }
310 
311  allocator_type get_allocator() const { return backing.get_allocator(); }
312 
313  void assign(std::initializer_list<el_val> v) {
314  clear();
315  for (auto& e : v) {
316  set(e.idx, e.var, e.val);
317  }
318  }
319  auto set(index_type idx, bool variadic, const_reference val) noexcept(
320  std::is_nothrow_copy_constructible_v<value_type>) {
321  return emplace(idx, variadic, val);
322  }
323 
324  template <typename... Args>
325  auto emplace(index_type idx, bool variadic, Args&&... args) noexcept(
326  std::is_nothrow_constructible_v<value_type, Args...>) {
327  assert_conditions();
328  // the offset is 2 because backing is required to hold one additional
329  // element after the last live_type
330  if (idx > static_cast<difference_type>(backing.size()) - 2) {
331  const auto start_adjusting =
332  static_cast<index_type>(backing.size()) - 1;
333  backing.resize(idx + 2);
334  if (start_adjusting >= 0) {
335  propagate_ref_end(start_adjusting);
336  }
337  assert_conditions();
338  }
339 
340  const typename BC::iterator pos = backing.begin() + idx;
341  bool need_adjust = !(std::holds_alternative<live_type>(*pos) &&
342  (std::get<live_type>(*pos).variadic == variadic));
343 
344  if (need_adjust) {
345  *pos =
346  live_type{value_type(std::forward<Args...>(args...)), variadic, 0};
347  propagate_ref_mid(idx);
348  } else {
349  std::get<live_type>(*pos).val =
350  value_type(std::forward<Args...>(args...));
351  }
352 
353  _first = std::min(_first, idx);
354  _size = std::count_if(backing.begin() + _first, backing.end(),
355  [](const element_type& e) {
356  return std::holds_alternative<live_type>(e);
357  });
358 
359  assert_conditions();
360 
361  if constexpr (AllowIteration) {
362  return iterator{pos};
363  } else {
364  return;
365  }
366  }
367 
368  [[nodiscard]] size_type size() const noexcept { return _size; }
369  [[nodiscard]] size_type capacity() const noexcept {
370  return backing.capacity();
371  }
372  [[nodiscard]] size_type empty() const noexcept { return backing.empty(); }
373  [[nodiscard]] size_type max_size() const noexcept {
374  return std::min(backing.max_size(), static_cast<size_type>(npos) - 1);
375  }
376 
377  void reserve(size_type s) { backing.reserve(s); }
378  void shrink_to_fit() { backing.shrink_to_fit(); }
379 
380  void clear() noexcept {
381  backing = {{tombstone_t{}}};
382  _first = npos;
383  _size = 0;
384  }
385 
386  const_reference operator[](index_type idx) const noexcept(false) {
387  // throws std::out_of_range on hitting tombstone
388  const auto pos = adjust_down<false>(
389  backing.begin() +
390  std::min(idx, static_cast<index_type>(backing.size()) - 1));
391  return std::get<live_type>(*pos).val;
392  }
393  reference operator[](index_type idx) noexcept(false) {
394  // throws std::out_of_range on hitting tombstone
395  const auto pos = adjust_down<false>(
396  backing.begin() +
397  std::min(idx, static_cast<index_type>(backing.size()) - 1));
398  return std::get<live_type>(*pos).val;
399  }
400 
401  info_t<pointer> info(index_type idx) noexcept {
402  const auto pos = adjust_down<true>(
403  backing.begin() +
404  std::min(idx, static_cast<index_type>(backing.size()) - 1));
405  if (auto p = std::get_if<live_type>(&*pos)) {
406  return {&p->val, pos - backing.begin(), p->variadic};
407  } else {
408  return {nullptr, pos - backing.begin(), false};
409  }
410  }
411 
412  info_t<const_pointer> info(index_type idx) const noexcept {
413  const auto pos = adjust_down<true>(
414  backing.begin() +
415  std::min(idx, static_cast<index_type>(backing.size()) - 1));
416  if (auto p = std::get_if<live_type>(&*pos)) {
417  return {&p->val, pos - backing.begin(), p->variadic};
418  } else {
419  return {nullptr, pos - backing.begin(), false};
420  }
421  }
422 
423  pointer get(index_type idx) noexcept { return info(idx).ptr; }
424 
425  const_pointer get(index_type idx) const noexcept { return info(idx).ptr; }
426 
427  iterator begin() noexcept { return iterator{backing.begin() + _first}; }
428  iterator end() noexcept { return iterator{backing.end()}; }
429  const_iterator begin() const noexcept {
430  return iterator{backing.begin() + _first};
431  }
432  const_iterator end() const noexcept { return iterator{backing.end()}; }
433 
434  reverse_iterator rbegin() noexcept {
435  return std::make_reverse_iterator(iterator{backing.end()});
436  }
437  reverse_iterator rend() noexcept {
438  return std::make_reverse_iterator(iterator{backing.begin() + _first});
439  }
440  const_reverse_iterator rbegin() const noexcept {
441  return std::make_reverse_iterator(iterator{backing.end()});
442  }
443  const_reverse_iterator rend() const noexcept {
444  return std::make_reverse_iterator(iterator{backing.begin() + _first});
445  }
446 
447  const_iterator cbegin() const noexcept {
448  return const_iterator{backing.begin() + _first};
449  }
450  const_iterator cend() const noexcept {
451  return const_iterator{backing.end()};
452  }
453  const_reverse_iterator crbegin() const noexcept {
454  return std::make_reverse_iterator(const_iterator{backing.end()});
455  }
456  const_reverse_iterator crend() const noexcept {
457  return std::make_reverse_iterator(
458  const_iterator{backing.begin() + _first});
459  }
460 
461  // template <typename _T, fda_policy _policy, typename _Alloc>
462  friend void swap(falldown_array<T, policy, Alloc>& a,
464  using std::swap;
465  swap(a.backing, b.backing);
466  swap(a._first, b._first);
467  swap(a._size, b._size);
468  }
469 
470  ~falldown_array() = default;
471 
472  private:
473  // Perform a lookup.
474  // In constant time, jump to the controlling element for the given index.
475  // If there is no such element, or if said element is nonvariadic, then:
476  // if nothrow: return the argument unchanged.
477  // else: throw std::out_of_range.
478 
479  template <typename F>
480  struct extract_arg_from;
481  template <typename R, typename Arg>
482  struct extract_arg_from<R (*)(Arg)> {
483  using type = Arg;
484  };
485  template <typename F>
486  struct extract_ret_from;
487  template <typename R, typename Arg>
488  struct extract_ret_from<R (*)(Arg)> {
489  using type = R;
490  };
491 
492  template <typename... Fs>
493  struct Funs : Funs<Fs>... {};
494  template <typename F>
495  struct Funs<F> {
496  using arg = typename extract_arg_from<F>::type;
497  using ret = typename extract_ret_from<F>::type;
498  ret operator()(arg a) { return f(a); }
499  F f;
500  };
501 
502  template <bool nothrow>
503  static typename BC::iterator
504  adjust_down(typename BC::iterator pos) noexcept(nothrow) {
505  static_assert(std::is_invocable_v<Funs<void (*)(int)>, int>);
506  static_assert(kblib::detail::v_invocable_with_all_v<
507  Funs<void (*)(tombstone_t), void (*)(live_type),
508  void (*)(backref_t)>,
509  element_type>);
510  return kblib::visit2(
511  *pos,
512  [&](tombstone_t) -> typename BC::iterator {
513  if constexpr (nothrow) {
514  return pos;
515  } else {
516  throw std::out_of_range("");
517  }
518  },
519  [&](live_type&) { return pos; },
520  [&](backref_t d) { return pos - d.dist_var; });
521  }
522 
523  // Starting from the previous live value, adjusts all back- and
524  // forward-references until it reaches the start position.
525  void propagate_ref_end(index_type start) noexcept {
526  const auto start_pos = backing.begin() + start;
527  const auto end = backing.end();
528  auto [count, variadic] = scan_backward_for_last_var(start + 1);
529  auto dist_nvar = scan_backward_for_last_entry(start + 1);
530  for (auto it = start_pos + 1;
531  it != end && !std::holds_alternative<live_type>(*it); ++it) {
532  if (variadic) {
533  *it = backref_t{count, dist_nvar};
534  } else {
535  *it = tombstone_t{count};
536  }
537  ++count;
538  ++dist_nvar;
539  }
540  }
541  // Repairs the structure of the falldown_array after a middle-insert. This
542  // entails scanning forward to set backreferences, and backward to set next
543  // indices.
544  void propagate_ref_mid(index_type start) noexcept {
545  const typename BC::iterator pos = backing.begin() + start;
546 
547  assert(start >= 0 and kblib::to_unsigned(start) < backing.size());
548  assert(std::holds_alternative<live_type>(*pos));
549  auto& reference = std::get<live_type>(*pos);
550 
551  propagate_ref_end(start);
552  if constexpr (AllowIteration) {
553  reference.next = scan_forward_for_next_entry(start + 1) - start;
554  if (reference.next + start == kblib::to_signed(backing.size())) {
555  reference.next = npos;
556  }
557  auto last = scan_backward_for_last_entry(start - 1);
558  if (last != start) {
559  std::get<live_type>(backing[last]).next = (start - last);
560  }
561  }
562 
563  return;
564  }
565 
566  // Perform a partial relative backward lookup:
567  // If DoShadow, find the nearest live element and return the distance as well
568  // as if it is variadic. Else, find the nearest variadic live element and
569  // return the distance and true. Else, if there is no nearest variadic live
570  // element, return the distance to the nearest nonvariadic live element, and
571  // false. Else, the search reached the beginning. Return the distance to the
572  // beginning, and false.
573  std::pair<difference_type, bool>
574  scan_backward_for_last_var(index_type start) const noexcept {
575  const typename BC::const_iterator pos = backing.begin() + start,
576  begin = backing.begin();
577  std::optional<difference_type> nvar_pos;
578  if (pos != begin) {
579  auto it = pos - 1;
580  difference_type steps = 0;
581  do {
582  ++steps;
583  if (auto p = std::get_if<live_type>(&*it)) {
584  if constexpr (DoShadow) {
585  return {steps, p->variadic};
586  } else {
587  if (p->variadic) {
588  return {steps, true};
589  } else if (!nvar_pos) {
590  nvar_pos.emplace(steps);
591  }
592  }
593  } else if (auto p = std::get_if<backref_t>(&*it)) {
594  return {steps + p->dist_var, true};
595  } else if (auto p = std::get_if<tombstone_t>(&*it)) {
596  if (nvar_pos) {
597  return {*nvar_pos, false};
598  }
599  return {steps + p->dist, false};
600  }
601  } while (--it != begin);
602  if (nvar_pos) {
603  return {*nvar_pos, false};
604  }
605  return {start, false};
606  } else {
607  return {0, false};
608  }
609  }
610  // Return the index of the next live value, starting at start.
611  index_type scan_forward_for_next_entry(index_type start) const noexcept {
612  return kblib::find_in_if(backing.begin() + start, backing.end(),
613  [](const element_type& el) {
614  return std::holds_alternative<live_type>(el);
615  }) +
616  start;
617  }
618  // Return the index of the previous live value, starting at start.
619  index_type scan_backward_for_last_entry(index_type start) const noexcept {
620  // Naive search:
621  return kblib::find_last_in_if(
622  backing.begin(), backing.begin() + start + 1,
623  [](const element_type& el) {
624  return std::holds_alternative<live_type>(el);
625  });
626 
627  // Assumes all preconditions are valid:
628  // const typename BC::const_iterator
629  // begin = backing.begin();
630  // typename BC::const_iterator
631  // pos = begin+start;
632  // for (index_type steps{}; pos != begin; ++steps,--pos) {
633  // if (std::holds_alternative<live_type>(*pos)) {
634  // return steps;
635  // } else if (auto p = std::get_if<backref_t>(&*pos)) {
636  // return steps + p->dist_var;
637  // } else if (auto p = std::get_if<tombstone_t>(&*pos)) {
638  // return steps + p->dist;
639  // }
640  // }
641  // return start;
642  }
643 
644  void assert_conditions() noexcept {
645  using namespace std::literals;
646  std::cerr << "--------\n";
647  if (!backing.empty()) {
648  {
649  bool var{false};
650  index_type counter{};
651  index_type toNext{_first};
652  [[maybe_unused]] index_type idx = 0;
653  for (const auto& el : backing) {
654  std::cerr << idx++ << " b: -" << counter << " n: " << toNext
655  << " v: " << var << " | ";
656  kblib::visit2(
657  el,
658  [&](tombstone_t) {
659  std::cerr << "X\n";
660  assert(!var);
661  },
662  [&](const live_type& e) {
663  std::cerr << '[' << e.val << ','
664  << (e.variadic ? 'T' : 'F');
665  if constexpr (AllowIteration) {
666  std::cerr << ',' << e.next;
667  }
668  std::cerr << "]\n";
669  if constexpr (DoShadow) {
670  var = e.variadic;
671  counter = 0;
672  } else {
673  if (e.variadic) {
674  var = true;
675  counter = 0;
676  }
677  }
678  assert(toNext == 0);
679  toNext = e.next;
680  },
681  [&](backref_t b) {
682  std::cerr << "b -" << b.dist_var << '\n';
683  assert(b.dist_var == counter);
684  assert(var);
685  });
686  --toNext;
687  ++counter;
688  }
689  }
690  assert(!std::holds_alternative<live_type>(backing.back()));
691  }
692  return; // all good
693  }
694 
695  BC backing{tombstone_t{}};
696  index_type _first{npos};
697  size_type _size{};
698 };
699 } // namespace wg_containers
700 
701 void c_test_func();
702 
703 #endif // FDA_H_INCLUDED
Definition: fda.h:126
Definition: fda.h:15
Definition: fda.h:243
Definition: fda.h:229
Definition: fda.h:75
Definition: fda.h:99
Definition: fda.h:91