diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 2da43c6b..3b2c2ca6 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -120,6 +120,38 @@ The documentation is written in [Goldmark](https://github.com/teekennedy/goldmar Hugo uses [Goldmark](https://github.com/teekennedy/goldmark-markdown) as its default Markdown processor for versions 0.60.0 and newer. It is built into Hugo, providing high-performance and fully [CommonMark](https://commonmark.org/)-compliant rendering, along with support for [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) Markdown. +### Documenting Algorithmic Complexity + +When documenting the complexity of an operation, use the `complexity` shortcode instead of writing raw Big-O text. +Place it on its own `**Complexity**` lines, right after the `**Description**` line/paragraph, for example: + +```markdown +**Description** +Returns the number of linked items. + +**Complexity** +{{< complexity "constant" "1" >}} +``` + +The shortcode takes two arguments: a complexity class (used to color and label it) and the expression to show +inside `O(...)`. The supported classes are: + +```markdown +**Complexity** +{{< complexity "constant" "1" >}} +**Complexity** +{{< complexity "linear" "N" >}}, where N is number of items in the tree, and assuming comparator is {{< complexity "constant" "1" >}}. +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where N is number of items in the tree. +**Complexity** +{{< complexity "linearithmic" "N log N" >}}, where N is number of items in the tree. +**Complexity** +{{< complexity "polynomial" "N²" >}}, where N is number of items in the tree. +**Complexity** +{{< complexity "constant" "1" >}} when something, or +{{< complexity "exponential" "2ᴺ" >}} some alternative, where N is number of items in the tree. +``` + ### Starting a New Page A template file is provided at `docs/page-template.md` to use as a starting point. Copy it to the diff --git a/docs/business-and-financial-case-for-commercial-sponsorship.md b/docs/business-and-financial-case-for-commercial-sponsorship.md index aa7683f4..956bbea3 100644 --- a/docs/business-and-financial-case-for-commercial-sponsorship.md +++ b/docs/business-and-financial-case-for-commercial-sponsorship.md @@ -2,7 +2,7 @@ title: "Why CFOs should care about Open Source" description: "The business and financial case for commercially sponsoring the Embedded Template Library (ETL)." date: 2026-09-12 -weight: 3003 +weight: 2 --- If your engineering team is using the **Embedded Template Library (ETL)** in commercial products, your company is benefiting from a critical piece of software infrastructure. diff --git a/docs/containers/trees/_index.md b/docs/containers/trees/_index.md new file mode 100644 index 00000000..c7bd6cc9 --- /dev/null +++ b/docs/containers/trees/_index.md @@ -0,0 +1,6 @@ +--- +title: "Trees" +weight: 100 +--- + +Tree like containers. diff --git a/docs/containers/trees/intrusive-avl-tree.md b/docs/containers/trees/intrusive-avl-tree.md new file mode 100644 index 00000000..ce52af92 --- /dev/null +++ b/docs/containers/trees/intrusive-avl-tree.md @@ -0,0 +1,560 @@ +--- +title: "intrusive_avl_tree" +weight: 1 +--- + +{{< callout type="info">}} + Header: `intrusive_avl_tree.h` + Similar to: `std::set` / `std::map` (ordering only, no ownership) +{{< /callout >}} + +An intrusive, self-balancing AVL tree. + +```cpp +template +etl::intrusive_avl_tree +``` + +`TValue` is the type that contains the actual values. It must derive from +`etl::intrusive_avl_tree_base::link_type`, exposed as the tree's own +`link_type`. Unlike `etl::intrusive_list`/`etl::intrusive_forward_list`, +which are templated directly on a link type and expect the node to derive +from it publicly, `intrusive_avl_tree` is templated only on `TValue` and an +`ID_`. Its `link_type` is a deliberately opaque wrapper around +`etl::tree_link` that exposes almost nothing as public (or even +protected) API, by design, so that inheriting from it imposes as little +"intrusive"-ness as possible, avoids name conflicts, and hides implementation +details. + +As with the other intrusive containers, an object may inherit multiple +`link_type`s with different `ID_` values to be a member of several trees (or +lists) at once: + +```cpp +typedef etl::intrusive_avl_tree_base<0>::link_type FirstLink; +typedef etl::intrusive_avl_tree_base<1>::link_type SecondLink; + +struct Item : public FirstLink, public SecondLink +{ + int value; +}; + +etl::intrusive_avl_tree tree0; +etl::intrusive_avl_tree tree1; +``` + +See also: +[Intrusive Links]({{% relref "../../utilities/intrusive-links.md" %}}) +[intrusive_list]({{% relref "../lists/intrusive-list.md" %}}) +[intrusive_forward_list]({{% relref "../lists/intrusive-forward-list.md" %}}) + +There is no class-level comparator template parameter. Every ordering +operation instead takes its own comparator functor at the call site. +Lookup functions (`find`, `lower_bound`, `upper_bound`, `find_or_insert`) +take a unary comparator accepting a single `const value_type&` and returning +an `int`: `>0` if the search target is "greater" than the argument, `0` if +found, `<0` if "smaller". The range constructor takes a binary comparator +`int(const value_type& lhs, const value_type& rhs)`; `etl::compare::cmp` +can be used directly if `value_type` defines `<`. + +## Member types + +```cpp +link_type (nested, inherited from etl::intrusive_avl_tree_base) +value_type TValue +pointer value_type* +const_pointer const value_type* +reference value_type& +const_reference const value_type& +size_type size_t +rvalue_reference value_type&& // C++11 +``` + +## The link type + +`TValue` must derive from `link_type`. It has its own set of special member +functions that a node author relies on. + +```cpp +link_type() +``` +**Description** +Default constructor. Leaves the item unlinked. + +--- + +```cpp +link_type(link_type&& other) +``` +**Description** +C++11. Constructs a new item by moving `other` into `this`. After +construction, `this` replaces `other` in the same tree position that +`other` occupied, so no tree balancing is needed. `other` becomes unlinked. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +link_type& operator=(link_type&& other) +``` +**Description** +C++11. Assigns `other` by moving it into `this`. Does nothing on +self-assignment. After assignment, `this` replaces `other` in the same tree +position that `other` occupied; `other` becomes unlinked. `this` might end +up in a different tree than it started in. + +**Complexity** +{{< complexity "logarithmic" "log N" >}} if `this` is already linked to a tree (it has to be erased first, with rebalancing), or +{{< complexity "constant" "1" >}} if `this` is not linked, where `N` is number of items in the tree. + +--- + +```cpp +link_type(const link_type&) = delete; +link_type& operator=(const link_type&) = delete; +``` +**Description** +C++11. Copy construction and assignment are disabled. + +--- + +```cpp +~link_type() +``` +**Description** +Might rotate the tree as necessary to keep it balanced. The tree is not the +real owner of its nodes' memory - the node's actual owner may destroy it at +any time, with or without a prior explicit `erase`. If the link is still +linked to a tree when it's destroyed, the destructor unlinks it +automatically; otherwise the node's former parent and children would keep +dangling pointers to it, breaking the tree and leading to undefined +behaviour. This is what makes it safe to embed a tree node in an object +whose lifetime is managed elsewhere. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree. + +## Constructors + +```cpp +etl::intrusive_avl_tree() +``` +**Description** +Default constructor. Constructs an empty tree. + +--- + +```cpp +template +etl::intrusive_avl_tree(TIterator first, TIterator last, TBinaryCompare binary_comp) +``` +**Description** +Constructs the tree from the range [`first`, `last`) of items, using +`binary_comp` to order them. All items in the range must be unlinked +initially; if any item is already linked to some other tree, throws +`etl::intrusive_avl_tree_value_is_already_linked`, and any items already +inserted are unlinked again - it's all or nothing. +If duplicates should be kept, `binary_comp` should return a non-zero result +even for "equal" items - `<0` prepends a duplicate, `>0` appends one. +Returning `0` for equal items keeps only the first of each run of duplicates +in the range; the rest are left unlinked. `etl::compare::cmp` +can be used directly as `binary_comp`. + +**Complexity** +{{< complexity "linearithmic" "N log N" >}}, where `N` is number of items in the range. + +--- + +```cpp +intrusive_avl_tree(intrusive_avl_tree&&) = default; +``` +**Description** +C++11. Move constructor. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +intrusive_avl_tree(const intrusive_avl_tree&) = delete; +``` +**Description** +C++11. Copy construction is disabled. + +--- + +```cpp +~intrusive_avl_tree() +``` +**Description** +Every remaining node is unlinked; none of the value objects are destroyed, +since the tree never owns their memory. + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is number of items in the tree. + +## Assignment + +```cpp +intrusive_avl_tree& operator=(intrusive_avl_tree&&) = default; +``` +**Description** +C++11. Move assignment. Does nothing on self-assignment. All of `this` +tree's former items have to be unlinked. + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is the size of `this` tree before assignment. + +--- + +```cpp +intrusive_avl_tree& operator=(const intrusive_avl_tree&) = delete; +``` +**Description** +C++11. Copy assignment is disabled. + +## Capacity + +```cpp +bool empty() const +``` +**Description** +Returns `true` if the tree has no linked items. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +size_t size() const +``` +**Description** +Returns the number of linked items. + +**Complexity** +{{< complexity "constant" "1" >}} + +## Iterators + +Only bidirectional `iterator`/`const_iterator` are provided; there is no +`reverse_iterator` (use `visit_in_order` with `is_reverse = true` instead). +An iterator may be in one of three logical states: it references a real +node (`has_value() == true`), it is the terminal sentinel (`it == end()`), +or it is "valueless" (`!has_value() && it != end()` - e.g. a +default-constructed iterator, or the result of `get_parent()`/`get_child()` +when there is no such node). + +```cpp +iterator begin() +const_iterator begin() const +const_iterator cbegin() const +``` +**Description** +Returns an iterator to the smallest item. Returns `end()` if the tree is +empty. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +iterator end() +const_iterator end() const +const_iterator cend() const +``` +**Description** +Returns an iterator to the terminal sentinel. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +iterator min() +const_iterator min() const +``` +**Description** +Equivalent to `begin()`. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +iterator max() +const_iterator max() const +``` +**Description** +Equivalent to `--end()`. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +iterator& operator++() +iterator operator++(int) +``` +**Description** +Advances the iterator to the next ("greater") item. + +**Complexity** +{{< complexity "constant" "1" >}} amortized, +{{< complexity "logarithmic" "log N" >}} worst-case, where `N` is number of items in the tree. + +--- + +```cpp +iterator& operator--() +iterator operator--(int) +``` +**Description** +Moves the iterator to the previous ("smaller") item. + +**Complexity** +{{< complexity "constant" "1" >}} amortized, +{{< complexity "logarithmic" "log N" >}} worst-case, where `N` is number of items in the tree. + +--- + +```cpp +reference operator*() const +pointer operator->() const +pointer get() const +``` +**Description** +Dereferences an iterator that has a value. `operator*`/`operator->` throw +`etl::intrusive_avl_tree_iterator_exception` if `has_value()` is `false`. +`get()` returns `nullptr` instead of throwing. + +--- + +```cpp +bool has_value() const +explicit operator bool() const +``` +**Description** +Returns `true` if the iterator references a real node. + +## Advanced traversal + +These are normally only needed for advanced traversal. + +```cpp +iterator get_root() +const_iterator get_root() const +``` +**Description** +Returns an iterator to the root node. Valueless (`has_value() == false`) if +the tree is empty. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +int_fast8_t get_balance_factor() const +``` +**Description** +Returns the node's AVL balance factor: `-1`, `0` or `+1`. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +iterator get_parent() const +``` +**Description** +Returns an iterator to the node's parent. Valueless if there is no parent. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +iterator get_child(bool is_right) const +``` +**Description** +Returns an iterator to the node's left (`is_right == false`) or right +(`is_right == true`) child. Valueless if there is no such child. + +**Complexity** +{{< complexity "constant" "1" >}} + +## Lookup + +```cpp +template +iterator find(TCompare comp) +template +const_iterator find(TCompare comp) const +``` +**Description** +Finds an item using the unary comparator `comp`. Returns `end()` if there +is no matching item. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree, +and assuming `comp` is {{< complexity "constant" "1" >}}. + +--- + +```cpp +template +iterator lower_bound(TCompare comp) +template +const_iterator lower_bound(TCompare comp) const +``` +**Description** +Returns an iterator to the first item that compares as "not less" than the +target of `comp`. Returns `end()` if no such item exists. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree, +and assuming `comp` is {{< complexity "constant" "1" >}}. + +--- + +```cpp +template +iterator upper_bound(TCompare comp) +template +const_iterator upper_bound(TCompare comp) const +``` +**Description** +Returns an iterator to the first item that compares as "greater" than the +target of `comp`. Returns `end()` if no such item exists. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree, +and assuming `comp` is {{< complexity "constant" "1" >}}. + +## Modifiers + +```cpp +template +etl::pair find_or_insert(TCompare comp, TFactory factory) +``` +**Description** +Finds an existing item using the unary comparator `comp`; if not found, +calls `factory()` (no arguments) and inserts the item it returns at the +position where the search stopped. This operation never invalidates +existing iterators, though depending on where the new item was linked, +existing iterators may skip over it. +`factory` must return the address of the new value (castable to +`link_type*`). If it returns `nullptr`, the tree is left unmodified and the +result iterator is valueless. If the returned value is already linked to +any tree, throws `etl::intrusive_avl_tree_value_is_already_linked`. +Returns a pair of: an iterator to the found or inserted item (never `end()`, +but may be valueless), and a `bool` that is `true` if the tree was modified. +If duplicates are wanted, `comp` should return a non-zero result even for +"equal" items - `+1` appends after existing duplicates, `-1` prepends +before them. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree, +and assuming `comp` and `factory` are {{< complexity "constant" "1" >}}. + +--- + +```cpp +iterator erase(iterator position) +iterator erase(const_iterator position) +``` +**Description** +Erases the item at `position`, including tree rebalancing. `position` must +reference a real item (asserts/throws +`etl::intrusive_avl_tree_iterator_exception` otherwise) and must originate +from this tree instance. Invalidates any existing iterator to the erased +item, but no others. Returns an iterator to the next node. +Use `clear()` instead if every item needs to be erased - no rebalancing is +involved. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +void clear() +``` +**Description** +Unlinks every item, leaving the tree empty. Invalidates all existing +iterators. This is cheaper than calling `erase` on every item, which would +cost {{< complexity "linearithmic" "N log N" >}} once intermediate +rebalancing is taken into account. + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +void swap(intrusive_avl_tree& other) +friend void swap(intrusive_avl_tree& lhs, intrusive_avl_tree& rhs) +``` +**Description** +Swaps the contents of two trees. Does nothing on self-swap. + +**Complexity** +{{< complexity "constant" "1" >}} + +## Traversal + +Each of these visits every item exactly once, without recursion, and has a +`const` overload. The visitor must not modify the tree during visitation. + +```cpp +template +void visit_in_order(bool is_reverse, Visitor visitor) +``` +**Description** +Visits every item in sorted order. `is_reverse` selects ascending (`false`) +or descending (`true`) order. + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +template +void visit_pre_order(bool is_reverse, Visitor visitor) +``` +**Description** +Visits every item in pre-order (parent before children). + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +template +void visit_post_order(bool is_reverse, Visitor visitor) +``` +**Description** +Visits every item in post-order (children before parent). + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is number of items in the tree. + +## Exceptions + +```cpp +intrusive_avl_tree_exception // base exception +intrusive_avl_tree_iterator_exception // invalid iterator use +intrusive_avl_tree_value_is_already_linked // item already linked to a tree +``` diff --git a/docs/sponsors.md b/docs/sponsors.md index 25d7fb00..77be5bbc 100644 --- a/docs/sponsors.md +++ b/docs/sponsors.md @@ -1,6 +1,6 @@ --- title: "Sponsors of the ETL" -weight: 3002 +weight: 3 --- A special thank you to all of our past and current sponsors. Your generosity directly funds the continuous development and maintenance of the ETL. diff --git a/docs/tutorials/_index.md b/docs/tutorials/_index.md index 43e6530f..d41ac916 100644 --- a/docs/tutorials/_index.md +++ b/docs/tutorials/_index.md @@ -13,6 +13,11 @@ A quick introduction to containers. --- +[Intrusive AVL Tree](./intrusive-avl-tree-tutorial) +Ordering objects that already exist, without a second container. + +--- + [observer](./observer-tutorial) A templated set of classes to allow easier implementation of the observer pattern. diff --git a/docs/tutorials/intrusive-avl-tree-tutorial.md b/docs/tutorials/intrusive-avl-tree-tutorial.md new file mode 100644 index 00000000..033ff380 --- /dev/null +++ b/docs/tutorials/intrusive-avl-tree-tutorial.md @@ -0,0 +1,210 @@ +--- +title: "Intrusive AVL Tree" +--- + +A tutorial on how `etl::intrusive_avl_tree` can give ordered, +{{< complexity "logarithmic" "log N" >}} access to objects that already +exist elsewhere, without a second container to track them. + +## Overview + +Imagine a set of driver/sensor tasks, each owned by its own subsystem. They +are created and destroyed at unpredictable times as modules attach and +detach, so there is no compile-time upper bound on how many exist at once. +A scheduler needs to visit the tasks that are currently due, in ascending +deadline order, on every tick. + +A normal fixed-capacity ordered container (`etl::set`/`etl::map`) would need +a second, separately-sized place to store pointers to these tasks. Instead, +embedding an `etl::intrusive_avl_tree` link directly into each task object +gives the scheduler ordered, {{< complexity "logarithmic" "log N" >}} +access to exactly the tasks that exist right now - nothing more is +allocated, and nothing needs to be kept in sync by hand. That's the payoff of "intrusive": the tree links the +objects that already exist, rather than storing copies of them. + +The example below also demonstrates two things that fall out of the design +for free: +- **Move support.** When the `std::vector` that owns the tasks reallocates, + every task is moved into new storage - and each move takes over its + task's exact tree position, with no rebalancing and no code of its own. +- **Safe removal without an explicit `erase()` call.** Removing a task's + owning object removes it from the schedule too, with no dangling entry + left behind - whether that's because the object is destroyed while still + linked (`link_type`'s destructor unlinks it automatically), or, as in + this example, because `std::vector::erase` move-assigns another task over + it (`link_type`'s move assignment unlinks `this` before taking over the + moved-from tree position). + +## Example + +```cpp +#include "etl/intrusive_avl_tree.h" + +#include +#include +#include +#include + +// A driver/sensor task that already exists as a long-lived object owned by +// its own subsystem. Inheriting from the tree's link_type is all it takes +// to make it schedulable - no separate fixed-capacity container is needed +// to track which tasks exist. +class SensorTask : public etl::intrusive_avl_tree_base<0>::link_type +{ +public: + SensorTask(uint32_t id_, uint32_t deadline_, uint32_t period_) + : id(id_), deadline(deadline_), period(period_) + { + } + + // Orders primarily by deadline; ties are broken by id so two tasks due + // on the same tick still occupy distinct tree positions. + struct Locate + { + uint32_t target_deadline; + uint32_t target_id; + + int operator()(const SensorTask& node) const + { + if (target_deadline != node.deadline) + { + return (target_deadline < node.deadline) ? -1 : 1; + } + if (target_id != node.id) + { + return (target_id < node.id) ? -1 : 1; + } + return 0; + } + }; + + uint32_t id; + uint32_t deadline; // next tick at which this task must be serviced + uint32_t period; +}; + +typedef etl::intrusive_avl_tree Schedule; + +// The only place a task gets linked - called whenever a module attaches, +// or whenever a task is rescheduled after being serviced. +void schedule_task(Schedule& schedule, SensorTask& task) +{ + schedule.find_or_insert(SensorTask::Locate{ task.deadline, task.id }, + [&task] { return &task; }); +} + +// Visits every currently-due task in ascending deadline order, services it, +// then reschedules it at its next deadline. +void service_due_tasks(Schedule& schedule, uint32_t now) +{ + for (auto it = schedule.begin(); (it != schedule.end()) && (it->deadline <= now); ) + { + SensorTask& task = *it; + it = schedule.erase(it); // O(log N), rebalances + + std::cout << "servicing task " << task.id << " (was due at " << task.deadline << ")\n"; + + task.deadline = now + task.period; + schedule_task(schedule, task); // reschedule at the new deadline + } +} + +void print_schedule(const Schedule& schedule) +{ + for (auto it = schedule.begin(); it != schedule.end(); ++it) + { + std::cout << " task " << it->id << " due at " << it->deadline << "\n"; + } +} + +int main() +{ + Schedule schedule; + + // Driver registry: a plain std::vector that a subsystem uses to own its + // tasks for whatever reason it already needs to - the tree does not + // require this vector to exist, it's just a stand-in for "wherever the + // real owner happens to keep the object". + std::vector driver_registry; + driver_registry.reserve(2); + + driver_registry.emplace_back(1, 100, 1000); + schedule_task(schedule, driver_registry.back()); + + driver_registry.emplace_back(2, 300, 1000); + schedule_task(schedule, driver_registry.back()); + + std::cout << "Initial schedule (ascending deadline):\n"; + print_schedule(schedule); + + std::cout << "\nschedule.size() before growth: " << schedule.size() << "\n"; + + // driver_registry.reserve()'d capacity was 2; this emplace_back forces a + // reallocation. std::vector move-constructs every existing SensorTask + // into the new storage. Each move steals its task's exact tree position + // (O(1), no rebalancing) - the tree needs no code of its own to survive + // the underlying storage moving. + driver_registry.emplace_back(3, 200, 1100); + schedule_task(schedule, driver_registry.back()); + + std::cout << "schedule.size() after growth: " << schedule.size() << "\n\n"; + + std::cout << "Schedule after growth (still correctly ordered):\n"; + print_schedule(schedule); + + std::cout << "\nServicing due tasks at tick 250:\n"; + service_due_tasks(schedule, 250); + + std::cout << "\nSchedule after servicing:\n"; + print_schedule(schedule); + + // Unplugging a module removes its SensorTask from driver_registry. Task 2 + // isn't the last element, so std::vector::erase() doesn't just destroy it + // in place: it move-assigns the following element (task 3) into task 2's + // slot, then destroys the now-duplicate last slot. That move-assignment + // is SensorTask's (implicitly-generated) one, which calls + // link_type::operator=(link_type&&): it first unlinks *this* - task 2's + // slot, still linked at deadline 300 - from the tree, then takes over + // task 3's tree position. The destructor that runs afterwards, on the + // vacated last slot, fires on an already-unlinked, moved-from object, so + // it has nothing left to do - no explicit schedule.erase() call is made + // here, and no dangling entry is left behind. + std::cout << "\nschedule.size() before unplugging task 2: " << schedule.size() << "\n"; + + auto pos = std::find_if(driver_registry.begin(), driver_registry.end(), + [](const SensorTask& t) { return t.id == 2; }); + driver_registry.erase(pos); // move-assigns task 3 over task 2, unlinking task 2 + + std::cout << "schedule.size() after unplugging task 2: " << schedule.size() << "\n"; + + return 0; +} +``` + +## Program output + +``` +Initial schedule (ascending deadline): + task 1 due at 100 + task 2 due at 300 + +schedule.size() before growth: 2 +schedule.size() after growth: 3 + +Schedule after growth (still correctly ordered): + task 1 due at 100 + task 3 due at 200 + task 2 due at 300 + +Servicing due tasks at tick 250: +servicing task 1 (was due at 100) +servicing task 3 (was due at 200) + +Schedule after servicing: + task 2 due at 300 + task 1 due at 1250 + task 3 due at 1350 + +schedule.size() before unplugging task 2: 3 +schedule.size() after unplugging task 2: 2 +``` diff --git a/docs/utilities/clamped-value.md b/docs/utilities/clamped-value.md index 5bae0543..2396a96a 100644 --- a/docs/utilities/clamped-value.md +++ b/docs/utilities/clamped-value.md @@ -26,7 +26,8 @@ including the header to override automatic detection. When `ETL_HAS_FLOATING_POINT_CLAMPED_VALUE` is `0`, `T` must be an integral type. When it is `1`, `float`, `double`, and `long double` may also be used with -runtime bounds. +runtime bounds. `clamped_value_range` and `referenced_clamped_value` are +available in C++11 and later. ```cpp // C++11 and later @@ -48,6 +49,20 @@ etl::clamped_value float_ct(0.5f); #endif ``` +In C++11 and later, multiple values can share one immutable runtime range: + +```cpp +const etl::clamped_value_range range(-1.5f, 2.5f); + +etl::referenced_clamped_value value1(range, 0.5f); +etl::referenced_clamped_value value2(range, 1.5f); +``` + +`referenced_clamped_value` stores an +`etl::reference_wrapper>`. The range must outlive +every value that refers to it. Construction from a temporary range is deleted. +Copying or swapping a referenced value also copies or swaps its range reference. + ## Types `difference_type` is the signed counterpart of `T` for integral values. For @@ -55,6 +70,9 @@ floating-point values, `difference_type` is `T`, allowing fractional steps. `limits_type` and `difference_limits_type` provide the corresponding `etl::numeric_limits` specializations. +`referenced_clamped_value` also defines `range_type` as +`clamped_value_range`. + ## Constructors ```cpp @@ -76,6 +94,23 @@ runtime-bound specialization. Supplying `Min` and `Max` selects fixed compile-time bounds. NaN bounds and initial values are rejected. Infinite bounds and values are permitted. +### Referenced ranges + +```cpp +clamped_value_range range(min, max); +referenced_clamped_value value(range); +referenced_clamped_value value(range, initial); +``` + +The one-argument value constructor initializes to `range.min()`. The +two-argument form clamps `initial` to the shared range. A range is immutable +after construction, so every referring value observes stable bounds. A +default-constructed range uses the full representable range of `T`. + +`referenced_clamped_value` is non-owning. The referenced range must outlive +the value and any copies of it. The constructors taking `range_type&&` are +deleted to prevent direct construction from a temporary range. + ## Modifiers ```cpp @@ -93,6 +128,10 @@ counterpart of `T` as its step type. For floating-point values, the step type is `T` and may be fractional. Steps saturate in constant time at the minimum or maximum rather than wrapping. NaN steps are rejected. +`referenced_clamped_value` supports `set(T)`, `to_min()`, `to_max()`, and +`advance(difference_type)`. It does not provide `set(min, max)` because its +range is immutable and shared. + Positive steps move toward `max`; negative steps move toward `min`. Infinite steps saturate at a finite bound. Infinite values and bounds remain valid. @@ -122,6 +161,14 @@ ETL_NODISCARD T max() const noexcept; Gets the current value and its bounds. Both compile-time and runtime specializations provide these accessors as const-qualified member functions. +`referenced_clamped_value` additionally provides: + +```cpp +ETL_NODISCARD const range_type& range() const noexcept; +``` + +This returns the exact `clamped_value_range` object referenced by the value. + ## Operators ```cpp @@ -143,6 +190,8 @@ floating-point values, assignment and compound-assignment reject NaN. Prefix increment and decrement return a reference to the updated object. Postfix increment and decrement return a copy of the value before it was updated. All four operations move by one and saturate at the applicable bound. +`referenced_clamped_value` provides the same conversion, assignment, +increment, decrement, and compound-assignment operators. ## Operations @@ -153,6 +202,10 @@ ETL_CONSTEXPR14 void swap(clamped_value& lhs, clamped_value& rhs); Swaps clamped values. Runtime values also swap their bounds. +Swapping two `referenced_clamped_value` objects swaps both their current +values and range references. Copy construction and copy assignment preserve +the source's range identity; they do not copy the range itself. + Equality, inequality, and relational comparisons are provided. Comparisons against arithmetic values use the implicit conversion to `T`. Equality compares the current values; runtime bounds do not affect equality. Runtime @@ -169,3 +222,124 @@ assignments, and steps also trigger an ETL assertion. Depending on the ETL error-handler configuration, an assertion may throw `etl::exception`, invoke a configured handler, or terminate. Operations that validate input are only `noexcept` when exceptions are disabled. + +The same validation applies when constructing `clamped_value_range` and +when assigning or advancing a `referenced_clamped_value`. + +## Shared Referenced Ranges + +`clamped_value_range` and `referenced_clamped_value` are available in +C++11 and later. They let independent values share immutable runtime bounds. + +### `clamped_value_range` + +```cpp +template +class clamped_value_range; +``` + +#### Constructors + +```cpp +clamped_value_range(); +clamped_value_range(T min, T max); +``` + +The default constructor uses the full representable range of `T`. The +two-argument constructor stores immutable bounds and rejects reversed or NaN +bounds. + +#### Accessors + +```cpp +ETL_NODISCARD T min() const noexcept; +ETL_NODISCARD T max() const noexcept; +``` + +### `referenced_clamped_value` + +```cpp +template +class referenced_clamped_value; +``` + +The class stores a current value and an +`etl::reference_wrapper>`. It does not own the +range, which must outlive the value and all copies of it. + +Member types are `range_type`, `difference_type`, `limits_type`, and +`difference_limits_type`, with the same numeric meanings as `clamped_value`. + +#### Constructors + +```cpp +explicit referenced_clamped_value(const range_type& range); +referenced_clamped_value(const range_type& range, T initial); +referenced_clamped_value(const referenced_clamped_value& other); + +referenced_clamped_value(range_type&&) = delete; +referenced_clamped_value(range_type&&, T) = delete; +``` + +The one-argument constructor initializes to `range.min()`. The two-argument +constructor clamps `initial`. Temporary ranges are rejected to prevent an +immediately dangling reference. Copy construction preserves the source value +and range identity. + +#### Modifiers + +```cpp +void set(T value); +void to_min() noexcept; +void to_max() noexcept; +void advance(difference_type n); +``` + +These operations mirror `clamped_value`. There is no `set(min, max)` because +the referenced range is immutable and shared. + +#### Accessors + +```cpp +ETL_NODISCARD T get() const noexcept; +ETL_NODISCARD T min() const noexcept; +ETL_NODISCARD T max() const noexcept; +ETL_NODISCARD const range_type& range() const noexcept; +ETL_NODISCARD operator T() const noexcept; +``` + +`range()` returns the exact range object held by reference. + +#### Operators + +```cpp +referenced_clamped_value& operator=(const referenced_clamped_value& other) & noexcept; +referenced_clamped_value& operator=(T value) &; +referenced_clamped_value& operator++() & noexcept; +referenced_clamped_value operator++(int) noexcept; +referenced_clamped_value& operator--() & noexcept; +referenced_clamped_value operator--(int) noexcept; +referenced_clamped_value& operator+=(difference_type n) &; +referenced_clamped_value& operator-=(difference_type n) &; +``` + +Copy assignment copies both the current value and range reference. Arithmetic +and value assignment clamp to the referenced bounds. Comparisons compare only +current values, not range identity or bounds. + +#### Swap + +```cpp +void swap(referenced_clamped_value& other) noexcept; +void swap(referenced_clamped_value& lhs, + referenced_clamped_value& rhs) noexcept; +``` + +Swap exchanges both current values and range references, keeping each value +associated with its original bounds. + +#### Errors + +Invalid or NaN range bounds trigger an ETL assertion. NaN initial values, +assignments, and steps are also rejected. Infinite values follow the same +behavior as `clamped_value`. diff --git a/docs/utilities/intrusive-links.md b/docs/utilities/intrusive-links.md index 6ee73e3f..ac16e511 100644 --- a/docs/utilities/intrusive-links.md +++ b/docs/utilities/intrusive-links.md @@ -297,6 +297,8 @@ template struct tree_link ``` +`etl::intrusive_avl_tree` is built on top of `tree_link` - see [intrusive_avl_tree]({{% relref "../containers/trees/intrusive-avl-tree.md" %}}). + ### Template parameters ```cpp const size_t ID_ diff --git a/hugo/assets/css/custom.css b/hugo/assets/css/custom.css index e23c0ebf..bbddd9ab 100644 --- a/hugo/assets/css/custom.css +++ b/hugo/assets/css/custom.css @@ -1,222 +1,6 @@ -/* Definitions for light mode */ -html:not(.dark) .highlight .chroma .c1 -{ - color: forestgreen !important; -} - -html:not(.dark) .highlight .chroma .cp, -html:not(.dark) .highlight .chroma .cpf { - color: mediumslateblue !important; -} - -html:not(.dark) h1 { - color: white !important; - width: 100%; - background-color: coral !important; - border: width 0.15rem solid rgb(163, 81, 51) !important; - padding: 0.5rem 0.5rem !important; -} - -html:not(.dark) h2 { - color: royalblue !important; - border-bottom: 3px solid black !important; -} - -html:not(.dark) h3, -html:not(.dark) h4 -{ - color: darkgoldenrod !important; - border-bottom: 2px solid darkgrey !important; -} - -html:not(.dark) p { - color: black !important; -} - -html:not(.dark) dl, -html:not(.dark) dl dt code, -html:not(.dark) dl dd { - color: black !important; - background-color: #eeeeee !important; -} - -html:not(.dark) code, -html:not(.dark) pre, -html:not(.dark) pre code { - color: black !important; - background-color: #eeeeee !important; -} - -html:not(.dark) hr { - background-color: #eeeeee !important; -} - -html:not(.dark) .hx\:font-extrabold { - font-family: Roboto, sans-serif !important; - font-size: 1.6rem !important; - color: royalblue !important; -} - -/* inactive */ -html:not(.dark) .hx\:text-gray-500 { - color: black !important; -} - -/* active */ -html:not(.dark) .hextra-sidebar-active-item { - color: black !important; -} - -html:not(.dark) .content p.label-text { - color: darkgoldenrod !important; -} - -/* Regular item hover */ -html:not(.dark) .hextra-sidebar-item a:hover, -html:not(.dark) .hextra-sidebar-item a:hover span { - background-color: rgb(250, 195, 175) !important; - color: black !important; -} - -/* Active item default + hover - must come AFTER regular hover */ -html:not(.dark) .hextra-sidebar-item[data-active="true"] a, -html:not(.dark) .hextra-sidebar-item[data-active="true"] a span, -html:not(.dark) .hextra-sidebar-item[data-active="true"] a:hover, -html:not(.dark) .hextra-sidebar-item[data-active="true"] a:hover span { - background-color: coral !important; - color: white !important; -} - - -/* Definitions for dark mode */ -html.dark .highlight .chroma .c1 -{ - color: lightgreen !important; -} - -html.dark .highlight .chroma .cp, -html.dark.highlight .chroma .cpf { - color: lightsteelblue !important; -} - -html.dark body { - background-color: #1e1e1e !important; -} - -html.dark main { - background-color: #252525 !important; -} - -html.dark .hx\:dark\:shadow-\[0_-12px_16px_\#111\] { - box-shadow: 0 -12px 16px #1e1e1e !important; -} - -html.dark .hx\:dark\:bg-dark { - background-color: #1e1e1e !important; - border-color: grey !important; -} - -html.dark h1 -{ - color: white !important; - width: 100%; - background-color: rgb(177, 87, 54) !important; - padding: 0.5rem 0.5rem !important; -} - -html.dark h2 { - color: #7099c7 !important; - border-bottom: 2px solid #c0c0c0 !important; -} - -html.dark h3, -html.dark h4 { - color: lightgreen !important; - border-bottom: 2px solid #505050 !important; -} - -html.dark p { - color: #c0c0c0 !important; -} - -html.dark p strong { - color: #c4c3a6; -} - -html.dark dl, -html.dark dl dt code, -html.dark dl dd { - color: #c0c0c0 !important; - background-color: #444444 !important; -} - -html.dark code, -html.dark pre, -html.dark pre code { - color: #c0c0c0 !important; - background-color: #444444 !important; -} - -html.dark hr { - background-color: #505050 !important; -} - -html.dark .hx\:font-extrabold { - font-family: Roboto, sans-serif !important; - font-size: 1.6rem !important; - color: #7099c7 !important; -} - -/* inactive */ -html.dark .hx\:text-gray-500 { - color: #c0c0c0 !important; -} - -/* active */ -html.dark .hextra-sidebar-active-item { - color: #c0c0c0 !important; -} - -html.dark .hextra-code-block .highlight .chroma span.p { - color: #c0c0c0; -} - -html.dark .content p.label-text { - color: salmon !important; -} - -/* Regular item hover */ -html.dark .hextra-sidebar-item a:hover, -html.dark .hextra-sidebar-item a:hover span { - background-color: rgb(250, 195, 175) !important; - color: black !important; -} - -/* Active item default + hover - must come AFTER regular hover */ -html.dark .hextra-sidebar-item[data-active="true"] a, -html.dark .hextra-sidebar-item[data-active="true"] a span, -html.dark .hextra-sidebar-item[data-active="true"] a:hover, -html.dark .hextra-sidebar-item[data-active="true"] a:hover span { - background-color: rgb(177, 87, 54) !important; - color: #e0e0e0 !important; -} - -/* Common definitions */ - -@font-face { - font-family: Roboto; - src: url('/fonts/roboto-v51-latin-regular.woff2') format('woff2'); - font-weight:500; - font-style: normal; -} - -@font-face { - font-family: 'Roboto Mono'; - src: url('/fonts/roboto-mono-v31-latin-500.woff2') format('woff2'); - font-weight: nornal; - font-style: normal; -} - +/**********************/ +/* .highlight .chroma */ +/**********************/ .highlight .chroma .k, .highlight .chroma .kc, .highlight .chroma .kd, @@ -233,64 +17,21 @@ html.dark .hextra-sidebar-item[data-active="true"] a:hover span { font-style: normal !important; } -.hx\:mb-0 { - display: block; - width: 100%; +html.dark .highlight .chroma .cp, +html.dark.highlight .chroma .cpf { + color: lightgreen !important; + color: lightsteelblue !important; } -.hextra-code-block { - margin-top: 0.5rem !important; - -} - -/* .highlight .chroma .c1 -{ +html:not(.dark) .highlight .chroma .cp, +html:not(.dark) .highlight .chroma .cpf { color: forestgreen !important; -} - -.highlight .chroma .cp, -.highlight .chroma .cpf { color: mediumslateblue !important; -} */ - -html.dark .hextra-code-block .highlight .chroma .cs, -html.dark .hextra-code-block .highlight .chroma .cp, -html.dark .hextra-code-block .highlight .chroma .cpf { - font-weight: normal; -} - -.badges { - display: flex; - flex-wrap: wrap; - gap: 4px; - justify-content: flex-start; - margin-top: 1rem; -} - -.badges p { - display: contents; - margin: 0; -} - -.badges div { - width: 100%; - display: flex; - flex-wrap: wrap; - gap: 4px; -} - -.content ul li { - font-family: Roboto, sans-serif !important; - font-style: normal !important; - font-weight: normal !important; - font-size: 1.1rem !important; -} - -.err { - background-color: transparent !important; - color: inherit !important; } +/***********************/ +/* h1 */ +/***********************/ h1 { font-family: Roboto, sans-serif !important; font-style: i !important; @@ -301,6 +42,25 @@ h1 { border-radius: 0.5rem !important; } +html.dark h1 +{ + color: white !important; + width: 100%; + background-color: rgb(177, 87, 54) !important; + padding: 0.5rem 0.5rem !important; +} + +html:not(.dark) h1 { + color: white !important; + width: 100%; + background-color: coral !important; + border: width 0.15rem solid rgb(163, 81, 51) !important; + padding: 0.5rem 0.5rem !important; +} + +/***********************/ +/* h2 */ +/***********************/ h2 { font-family: Roboto, sans-serif !important; font-style: normal !important; @@ -310,6 +70,19 @@ h2 { margin-bottom: 0.5em !important; } +html.dark h2 { + color: #7099c7 !important; + border-bottom: 2px solid #c0c0c0 !important; +} + +html:not(.dark) h2 { + color: royalblue !important; + border-bottom: 3px solid black !important; +} + +/***********************/ +/* h3 */ +/***********************/ h3 { font-family: Roboto, sans-serif !important; font-style: normal !important; @@ -321,6 +94,20 @@ h3 { #display: inline-block; } +html.dark h3 { + color: lightgreen !important; + border-bottom: 2px solid #505050 !important; +} + +html:not(.dark) h3 +{ + color: darkgoldenrod !important; + border-bottom: 2px solid darkgrey !important; +} + +/***********************/ +/* h4 */ +/***********************/ h4 { font-family: Roboto, sans-serif !important; font-style: normal !important; @@ -331,6 +118,43 @@ h4 { display: inline-block; } +html.dark h4 { + color: lightgreen !important; + border-bottom: 2px solid #505050 !important; +} + +html:not(.dark) h4 +{ + color: darkgoldenrod !important; + border-bottom: 2px solid darkgrey !important; +} + +/***********************/ +/* p */ +/***********************/ +p { + font-family: Roboto, sans-serif !important; + font-style: normal !important; + font-weight: normal !important; + font-size: 1.1rem !important; + margin-top: 0.5em !important; +} + +html.dark p { + color: #c0c0c0 !important; +} + +html.dark p strong { + color: #c4c3a6; +} + +html:not(.dark) p { + color: black !important; +} + +/***********************/ +/* dl */ +/***********************/ dl { font-family: 'Roboto Mono', monospace !important; font-style: normal !important; @@ -352,6 +176,366 @@ dl dd { font-size: 1rem !important; } +html.dark dl, +html.dark dl dt code, +html.dark dl dd { + color: #c0c0c0 !important; + background-color: #444444 !important; +} + +html:not(.dark) dl, +html:not(.dark) dl dt code, +html:not(.dark) dl dd { + color: black !important; + background-color: #eeeeee !important; +} + +/***********************/ +/* pre & code */ +/***********************/ +code { + font-family: 'Roboto Mono', monospace !important; + font-weight: normal !important; + font-style: normal !important; + font-size: 0.9rem !important; + border: none !important; +} + +pre { + font-family: 'Roboto Mono', monospace !important; + font-size: 0.9rem !important; + margin-top: 0em !important; + margin-bottom: 0em !important; + padding-left: 0.5em !important; + padding-right: 0.5em !important; + border: none !important; +} + +pre code { + font-family: 'Roboto Mono', monospace !important; + font-style: normal !important; + font-weight: normal !important; + font-size: 0.9rem !important; + margin-left: 0 !important; + border: none !important; +} + +html.dark code, +html.dark pre, +html.dark pre code { + color: #c0c0c0 !important; + background-color: #444444 !important; +} + +html:not(.dark) code, +html:not(.dark) pre, +html:not(.dark) pre code { + color: black !important; + background-color: #eeeeee !important; +} + +/***********************/ +/* hr */ +/***********************/ +hr { + height: 2px !important; + border: none !important; + margin-top: 1em !important; + margin-bottom: 1em !important; +} + +html.dark hr { + background-color: #505050 !important; +} + +html:not(.dark) hr { + background-color: #eeeeee !important; +} + +/***********************/ +/* body */ +/***********************/ +html.dark body { + background-color: #1e1e1e !important; +} + +/***********************/ +/* .hx\:font-extrabold */ +/***********************/ +html.dark .hx\:font-extrabold { + font-family: Roboto, sans-serif !important; + font-size: 1.6rem !important; + color: #7099c7 !important; +} + +html:not(.dark) .hx\:font-extrabold { + font-family: Roboto, sans-serif !important; + font-size: 1.6rem !important; + color: royalblue !important; +} + +/***********************/ +/* .hx\:text-gray-500 */ +/* inactive */ +/***********************/ +html.dark .hx\:text-gray-500 { + color: #c0c0c0 !important; +} + +html:not(.dark) .hx\:text-gray-500 { + color: black !important; +} + +/*******************************/ +/* .hextra-sidebar-active-item */ +/* active */ +/*******************************/ +html.dark .hextra-sidebar-active-item { + color: #c0c0c0 !important; +} + +html:not(.dark) .hextra-sidebar-active-item { + color: black !important; +} + +/*************************/ +/* .content p.label-text */ +/*************************/ +html.dark .content p.label-text { + color: salmon !important; +} + +html:not(.dark) .content p.label-text { + color: darkgoldenrod !important; +} + +/********************************/ +/* .hextra-sidebar-item a:hover */ +/********************************/ +html.dark .hextra-sidebar-item a:hover, +html.dark .hextra-sidebar-item a:hover span { + background-color: rgb(250, 195, 175) !important; + color: black !important; +} + +html:not(.dark) .hextra-sidebar-item a:hover, +html:not(.dark) .hextra-sidebar-item a:hover span { + background-color: rgb(250, 195, 175) !important; + color: black !important; +} + +/*********************************/ +/* .hextra-sidebar-item */ +/* Active item default + hover */ +/* Must come AFTER regular hover */ +/*********************************/ +html.dark .hextra-sidebar-item[data-active="true"] a, +html.dark .hextra-sidebar-item[data-active="true"] a span, +html.dark .hextra-sidebar-item[data-active="true"] a:hover, +html.dark .hextra-sidebar-item[data-active="true"] a:hover span { + background-color: rgb(177, 87, 54) !important; + color: #e0e0e0 !important; +} + +html:not(.dark) .hextra-sidebar-item[data-active="true"] a, +html:not(.dark) .hextra-sidebar-item[data-active="true"] a span, +html:not(.dark) .hextra-sidebar-item[data-active="true"] a:hover, +html:not(.dark) .hextra-sidebar-item[data-active="true"] a:hover span { + background-color: coral !important; + color: white !important; +} + +/***********************/ +/* main */ +/***********************/ +html.dark main { + background-color: #252525 !important; +} + +/***********************/ +/* .hx\:dark\:shadow */ +/***********************/ +html.dark .hx\:dark\:shadow-\[0_-12px_16px_\#111\] { + box-shadow: 0 -12px 16px #1e1e1e !important; +} + +/***********************/ +/* .hx\:dark\:bg-dark */ +/***********************/ +html.dark .hx\:dark\:bg-dark { + background-color: #1e1e1e !important; + border-color: grey !important; +} + +/***********************/ +/* hextra-code-block */ +/***********************/ +.hextra-code-block { + margin-top: 0.5rem !important; +} + +html.dark .hextra-code-block .highlight .chroma span.p { + color: #c0c0c0; +} + +html.dark .hextra-code-block .highlight .chroma .cs, +html.dark .hextra-code-block .highlight .chroma .cp, +html.dark .hextra-code-block .highlight .chroma .cpf { + font-weight: normal; +} + +/***********************/ +/* complexity */ +/***********************/ +.complexity { + padding: 3px 6px; + border-radius: 0.5rem !important; + font-weight: 600; + font-size: 0.9rem !important; + letter-spacing: 0.04em; + border: 1px solid transparent; +} + +html.dark .complexity { + &.constant { + color: #7CFFB2; + background: rgba(0,255,120,0.08); + border-color: rgba(0,255,120,0.25); + } + &.logarithmic { + color: #7CE7FF; + background: rgba(0,200,255,0.08); + border-color: rgba(0,200,255,0.18); + } + &.linear { + color: #6AB7FF; + background: rgba(80,140,255,0.08); + border-color: rgba(80,140,255,0.18); + } + &.linearithmic { + color: #B78CFF; + background: rgba(140,80,255,0.08); + border-color: rgba(140,80,255,0.18); + } + &.polynomial { + color: #FFD36A; + background: rgba(255,190,0,0.08); + border-color: rgba(255,190,0,0.18); + } + &.exponential { + color: #ff7d9c; + background: rgba(255,0,80,0.08); + border-color: rgba(255,0,80,0.2); + } +} + +html:not(.dark) .complexity { + &.constant { + color: #15803D; + background: rgba(34,197,94,0.10); + border-color: rgba(34,197,94,0.22); + } + + &.logarithmic { + color: #0E7490; + background: rgba(6,182,212,0.10); + border-color: rgba(6,182,212,0.22); + } + + &.linear { + color: #2563EB; + background: rgba(59,130,246,0.10); + border-color: rgba(59,130,246,0.22); + } + + &.linearithmic { + color: #7C3AED; + background: rgba(139,92,246,0.10); + border-color: rgba(139,92,246,0.22); + } + + &.polynomial { + color: #CA8A04; + background: rgba(234,179,8,0.12); + border-color: rgba(234,179,8,0.24); + } + + &.exponential { + color: #DC2626; + background: rgba(239,68,68,0.10); + border-color: rgba(239,68,68,0.22); + } +} + +/***********************/ +/* font-face */ +/***********************/ +@font-face { + font-family: Roboto; + src: url('/fonts/roboto-v51-latin-regular.woff2') format('woff2'); + font-weight:500; + font-style: normal; +} + +@font-face { + font-family: 'Roboto Mono'; + src: url('/fonts/roboto-mono-v31-latin-500.woff2') format('woff2'); + font-weight: nornal; + font-style: normal; +} + +/***********************/ +/* .hx\:mb */ +/***********************/ +.hx\:mb-0 { + display: block; + width: 100%; +} + +/***********************/ +/* .badges */ +/***********************/ +.badges { + display: flex; + flex-wrap: wrap; + gap: 4px; + justify-content: flex-start; + margin-top: 1rem; +} + +.badges p { + display: contents; + margin: 0; +} + +.badges div { + width: 100%; + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +/***********************/ +/* .content */ +/***********************/ +.content ul li { + font-family: Roboto, sans-serif !important; + font-style: normal !important; + font-weight: normal !important; + font-size: 1.1rem !important; +} + +/***********************/ +/* .err */ +/***********************/ +.err { + background-color: transparent !important; + color: inherit !important; +} + +/***********************/ +/* ol */ +/***********************/ ol { font-family: 'Roboto Mono', monospace !important; font-style: normal !important; @@ -373,51 +557,10 @@ ol li code { font-size: 1rem !important; } -code { - font-family: 'Roboto Mono', monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 0.9rem !important; - border: none !important; -} - -pre code { - font-family: 'Roboto Mono', monospace !important; - font-style: normal !important; - font-weight: normal !important; - font-size: 0.9rem !important; - margin-left: 0 !important; - border: none !important; -} - -pre { - font-family: 'Roboto Mono', monospace !important; - font-size: 0.9rem !important; - margin-top: 0em !important; - margin-bottom: 0em !important; - padding-left: 0.5em !important; - padding-right: 0.5em !important; - border: none !important; -} - -hr { - height: 2px !important; - border: none !important; - margin-top: 1em !important; - margin-bottom: 1em !important; -} - -p { - font-family: Roboto, sans-serif !important; - font-style: normal !important; - font-weight: normal !important; - font-size: 1.1rem !important; - margin-top: 0.5em !important; -} - +/***********************/ +/* tr */ +/***********************/ tr td code{ font-family: 'Roboto Mono', monospace !important; font-size: 0.8rem !important; } - - diff --git a/hugo/layouts/shortcodes/complexity.html b/hugo/layouts/shortcodes/complexity.html new file mode 100644 index 00000000..644440ab --- /dev/null +++ b/hugo/layouts/shortcodes/complexity.html @@ -0,0 +1,3 @@ + + 𝐎({{ .Get 1 }}) + diff --git a/include/etl/clamped_value.h b/include/etl/clamped_value.h index d7f3dcf9..37385139 100644 --- a/include/etl/clamped_value.h +++ b/include/etl/clamped_value.h @@ -35,6 +35,7 @@ SOFTWARE. #include "absolute.h" #include "algorithm.h" #include "error_handler.h" +#include "functional.h" #include "limits.h" #include "math.h" #include "static_assert.h" @@ -978,6 +979,400 @@ namespace etl T min_value; T max_value; }; + +#if ETL_USING_CPP11 + //*************************************************************************** + /// An immutable pair of runtime limits that may be shared by multiple + /// referenced_clamped_value objects. + ///@tparam T An integral type, or a floating-point type when + /// ETL_HAS_FLOATING_POINT_CLAMPED_VALUE is `1`. + ///\ingroup clamped_value + //*************************************************************************** + template + class clamped_value_range + { + public: + + typedef typename private_clamped_value::traits::limits_type limits_type; + + //************************************************************************* + /// Constructs the full representable range of T. + //************************************************************************* + ETL_CONSTEXPR14 clamped_value_range() + : min_value(limits_type::lowest()) + , max_value(limits_type::max()) + { + } + + //************************************************************************* + /// Constructs a range with the supplied limits. + /// Reversed bounds and NaN bounds are rejected. + ///\param min_ The minimum value. + ///\param max_ The maximum value. + //************************************************************************* + ETL_CONSTEXPR14 clamped_value_range(T min_, T max_) + : min_value(min_) + , max_value(max_) + { + private_clamped_value::validate(min_); + private_clamped_value::validate(max_); + ETL_ASSERT(min_ <= max_, ETL_ERROR_GENERIC("clamped_value_range: invalid range")); + } + + //************************************************************************* + /// Gets the minimum value. + //************************************************************************* + ETL_NODISCARD ETL_CONSTEXPR T min() const ETL_NOEXCEPT + { + return min_value; + } + + //************************************************************************* + /// Gets the maximum value. + //************************************************************************* + ETL_NODISCARD ETL_CONSTEXPR T max() const ETL_NOEXCEPT + { + return max_value; + } + + private: + + const T min_value; + const T max_value; + }; + + //*************************************************************************** + /// Provides a value clamped to a shared immutable runtime range. + /// The referenced range must outlive this object. + ///@tparam T An integral type, or a floating-point type when + /// ETL_HAS_FLOATING_POINT_CLAMPED_VALUE is `1`. + ///\ingroup clamped_value + //*************************************************************************** + template + class referenced_clamped_value + { + public: + + /// The referenced range type. + typedef clamped_value_range range_type; + + /// The type used to advance or subtract from the stored value. + typedef typename private_clamped_value::traits::difference_type difference_type; + + /// Numeric limits for the stored value type. + typedef typename private_clamped_value::traits::limits_type limits_type; + + /// Numeric limits for difference_type. + typedef typename private_clamped_value::traits::difference_limits_type difference_limits_type; + + //************************************************************************* + /// Constructs a value initialized to the minimum of the referenced range. + ///\param range_ The range to reference. It must outlive this object. + //************************************************************************* + explicit referenced_clamped_value(const range_type& range_) + : value(range_.min()) + , value_range(etl::cref(range_)) + { + } + + //************************************************************************* + /// Constructs a value clamped to the referenced range. + ///\param range_ The range to reference. It must outlive this object. + ///\param initial The initial value. + //************************************************************************* + referenced_clamped_value(const range_type& range_, T initial) + : value(private_clamped_value::validated_clamp(initial, range_.min(), range_.max())) + , value_range(etl::cref(range_)) + { + } + + //************************************************************************* + /// Prevents construction with a temporary range. + //************************************************************************* + referenced_clamped_value(range_type&&) = delete; + referenced_clamped_value(range_type&&, T) = delete; + + //************************************************************************* + /// Copies the value and its range reference. + ///\param other The value to copy. + //************************************************************************* + referenced_clamped_value(const referenced_clamped_value& other) ETL_NOEXCEPT + : value(other.value) + , value_range(other.value_range) + { + } + + //************************************************************************* + /// Copy assignment. Copies the value and its range reference. + ///\param other The value and range reference to copy. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator=(const referenced_clamped_value& other) ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT + { + if (this != &other) + { + value = other.value; + value_range = other.value_range; + } + + return *this; + } + + //************************************************************************* + /// Sets the value and clamps it to the referenced range. + ///\param value_ The value to assign. + //************************************************************************* + void set(T value_) + { + value = private_clamped_value::validated_clamp(value_, min(), max()); + } + + //************************************************************************* + /// Resets the value to the minimum of the referenced range. + //************************************************************************* + void to_min() ETL_NOEXCEPT + { + value = min(); + } + + //************************************************************************* + /// Resets the value to the maximum of the referenced range. + //************************************************************************* + void to_max() ETL_NOEXCEPT + { + value = max(); + } + + //************************************************************************* + /// Advances the value and saturates it at the referenced bounds. + ///\param n The difference to add. + //************************************************************************* + void advance(difference_type n) + { + private_clamped_value::validate(n); + value = private_clamped_value::advance(value, min(), max(), n); + } + + //************************************************************************* + /// Converts to the underlying value type. + ///\return The current value. + //************************************************************************* + ETL_NODISCARD operator T() const ETL_NOEXCEPT + { + return value; + } + + //************************************************************************* + /// Advances by one and saturates at the maximum. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator++() ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT + { + value = private_clamped_value::advance(value, min(), max(), difference_type(1)); + return *this; + } + + //************************************************************************* + /// Advances by one and returns the previous value. + ///\return The value before incrementing. + //************************************************************************* + referenced_clamped_value operator++(int) ETL_NOEXCEPT + { + referenced_clamped_value temp(*this); + ++(*this); + return temp; + } + + //************************************************************************* + /// Subtracts one and saturates at the minimum. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator--() ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT + { + value = private_clamped_value::advance(value, min(), max(), difference_type(-1)); + return *this; + } + + //************************************************************************* + /// Subtracts one and returns the previous value. + ///\return The value before decrementing. + //************************************************************************* + referenced_clamped_value operator--(int) ETL_NOEXCEPT + { + referenced_clamped_value temp(*this); + --(*this); + return temp; + } + + //************************************************************************* + /// Assigns a value and clamps it to the referenced range. + ///\param value_ The value to assign. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator=(T value_) ETL_LVALUE_REF_QUALIFIER + { + set(value_); + return *this; + } + + //************************************************************************* + /// Adds a difference and clamps to the referenced range. + ///\param n The difference to add. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator+=(difference_type n) ETL_LVALUE_REF_QUALIFIER + { + advance(n); + return *this; + } + + //************************************************************************* + /// Subtracts a difference and clamps to the referenced range. + ///\param n The difference to subtract. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator-=(difference_type n) ETL_LVALUE_REF_QUALIFIER + { + private_clamped_value::validate(n); + value = private_clamped_value::subtract(value, min(), max(), n); + return *this; + } + + //************************************************************************* + /// Gets the current value. + ///\return The current value. + //************************************************************************* + ETL_NODISCARD + T get() const ETL_NOEXCEPT + { + return value; + } + + //************************************************************************* + /// Gets the minimum of the referenced range. + ///\return The minimum value. + //************************************************************************* + ETL_NODISCARD + T min() const ETL_NOEXCEPT + { + return value_range.get().min(); + } + + //************************************************************************* + /// Gets the maximum of the referenced range. + ///\return The maximum value. + //************************************************************************* + ETL_NODISCARD + T max() const ETL_NOEXCEPT + { + return value_range.get().max(); + } + + //************************************************************************* + /// Gets the referenced range. + ///\return The referenced range. + //************************************************************************* + ETL_NODISCARD + const range_type& range() const ETL_NOEXCEPT + { + return value_range.get(); + } + + //************************************************************************* + /// Swaps the values and range references. + ///\param other The value to swap with. + //************************************************************************* + void swap(referenced_clamped_value& other) ETL_NOEXCEPT + { + using ETL_OR_STD::swap; + swap(value, other.value); + swap(value_range, other.value_range); + } + + //************************************************************************* + /// Swaps the values and range references. + ///\param lhs The first value. + ///\param rhs The second value. + //************************************************************************* + friend void swap(referenced_clamped_value& lhs, referenced_clamped_value& rhs) ETL_NOEXCEPT + { + lhs.swap(rhs); + } + + //************************************************************************* + /// Compares current values for equality. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if the current values are equal. + //************************************************************************* + friend bool operator==(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + #include "private/diagnostic_float_equal_push.h" + return lhs.value == rhs.value; + #include "private/diagnostic_pop.h" + } + + //************************************************************************* + /// Compares current values for inequality. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if the current values differ. + //************************************************************************* + friend bool operator!=(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + return !(lhs == rhs); + } + + //************************************************************************* + /// Compares current values using less-than. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if lhs is less than rhs. + //************************************************************************* + friend bool operator<(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + return lhs.value < rhs.value; + } + + //************************************************************************* + /// Compares current values using less-than-or-equal. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if lhs is less than or equal to rhs. + //************************************************************************* + friend bool operator<=(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + return !(rhs < lhs); + } + + //************************************************************************* + /// Compares current values using greater-than. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if lhs is greater than rhs. + //************************************************************************* + friend bool operator>(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + return rhs < lhs; + } + + //************************************************************************* + /// Compares current values using greater-than-or-equal. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if lhs is greater than or equal to rhs. + //************************************************************************* + friend bool operator>=(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + return !(lhs < rhs); + } + + private: + + T value; + etl::reference_wrapper value_range; + }; +#endif } // namespace etl #endif diff --git a/test/syntax_check/clamped_value.h.t.cpp b/test/syntax_check/clamped_value.h.t.cpp index 7651ff8c..31f6d549 100644 --- a/test/syntax_check/clamped_value.h.t.cpp +++ b/test/syntax_check/clamped_value.h.t.cpp @@ -37,3 +37,13 @@ etl::clamped_value clamped_double_run_time; #if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE etl::clamped_value clamped_float_compile_time; #endif + +#if ETL_USING_CPP11 +const etl::clamped_value_range referenced_clamped_range(0, 10); +etl::referenced_clamped_value referenced_clamped_int(referenced_clamped_range); + + #if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE +const etl::clamped_value_range referenced_clamped_float_range(-1.0f, 1.0f); +etl::referenced_clamped_value referenced_clamped_float(referenced_clamped_float_range); + #endif +#endif diff --git a/test/test_clamped_value.cpp b/test/test_clamped_value.cpp index cdf81420..09a64781 100644 --- a/test/test_clamped_value.cpp +++ b/test/test_clamped_value.cpp @@ -531,5 +531,157 @@ namespace } #endif #endif + +#if ETL_USING_CPP11 + //************************************************************************* + TEST(test_referenced_clamped_value_uses_shared_range) + { + const etl::clamped_value_range range(2, 7); + etl::referenced_clamped_value value1(range, 1); + etl::referenced_clamped_value value2(range, 8); + + CHECK_EQUAL(2, value1.get()); + CHECK_EQUAL(7, value2.get()); + CHECK_EQUAL(2, value1.min()); + CHECK_EQUAL(7, value1.max()); + CHECK_EQUAL(&range, &value1.range()); + CHECK_EQUAL(&range, &value2.range()); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_arithmetic_saturates) + { + const etl::clamped_value_range range(-2, 3); + etl::referenced_clamped_value value(range, 0); + + value += 2; + CHECK_EQUAL(2, value.get()); + ++value; + ++value; + CHECK_EQUAL(3, value.get()); + value -= 4; + CHECK_EQUAL(-1, value.get()); + --value; + --value; + CHECK_EQUAL(-2, value.get()); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_modifiers_and_postfix_operators) + { + const etl::clamped_value_range range(2, 7); + etl::referenced_clamped_value value(range); + + CHECK_EQUAL(2, value.get()); + value.set(4); + CHECK_EQUAL(4, value.get()); + + etl::referenced_clamped_value previous = value++; + CHECK_EQUAL(4, previous.get()); + CHECK_EQUAL(5, value.get()); + + previous = value--; + CHECK_EQUAL(5, previous.get()); + CHECK_EQUAL(4, value.get()); + + value.to_max(); + CHECK_EQUAL(7, value.get()); + value.to_min(); + CHECK_EQUAL(2, value.get()); + + value = 100; + CHECK_EQUAL(7, value.get()); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_default_range_and_comparisons) + { + const etl::clamped_value_range default_range; + const etl::clamped_value_range range(0, 10); + etl::referenced_clamped_value value1(range, 3); + etl::referenced_clamped_value value2(range, 4); + const int converted = value1; + + CHECK_EQUAL(etl::numeric_limits::lowest(), default_range.min()); + CHECK_EQUAL(etl::numeric_limits::max(), default_range.max()); + CHECK_EQUAL(3, converted); + CHECK(value1 == value1); + CHECK(value1 != value2); + CHECK(value1 < value2); + CHECK(value1 <= value2); + CHECK(value2 > value1); + CHECK(value2 >= value1); + CHECK(value1 < 4); + CHECK(3 == value1); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_copy_assignment_and_swap_ranges) + { + const etl::clamped_value_range range1(0, 10); + const etl::clamped_value_range range2(20, 30); + etl::referenced_clamped_value value1(range1, 4); + etl::referenced_clamped_value value2(range2, 24); + + etl::referenced_clamped_value copy(value1); + CHECK_EQUAL(&range1, ©.range()); + CHECK_EQUAL(4, copy.get()); + + copy = value2; + CHECK_EQUAL(&range2, ©.range()); + CHECK_EQUAL(24, copy.get()); + + swap(value1, value2); + CHECK_EQUAL(&range2, &value1.range()); + CHECK_EQUAL(24, value1.get()); + CHECK_EQUAL(&range1, &value2.range()); + CHECK_EQUAL(4, value2.get()); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_rejects_invalid_range) + { + CHECK_THROW((etl::clamped_value_range(7, 2)), etl::exception); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_rejects_temporary_range) + { + typedef etl::clamped_value_range range_type; + typedef etl::referenced_clamped_value value_type; + + CHECK_FALSE((etl::is_constructible::value)); + CHECK_FALSE((etl::is_constructible::value)); + } + + #if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE + //************************************************************************* + TEST(test_referenced_floating_clamped_value) + { + const etl::clamped_value_range range(-1.5F, 2.5F); + etl::referenced_clamped_value value(range, 0.5F); + + value += 1.25F; + CHECK_CLOSE(1.75F, value.get(), 0.0001F); + value.advance(10.0F); + CHECK_CLOSE(2.5F, value.get(), 0.0001F); + } + + //************************************************************************* + TEST(test_referenced_floating_clamped_value_rejects_nan) + { + const float nan = etl::numeric_limits::quiet_NaN(); + + CHECK_THROW((etl::clamped_value_range(nan, 1.0F)), etl::exception); + + const etl::clamped_value_range range(-1.0F, 1.0F); + CHECK_THROW((etl::referenced_clamped_value(range, nan)), etl::exception); + + etl::referenced_clamped_value value(range, 0.0F); + CHECK_THROW(value = nan, etl::exception); + CHECK_THROW(value.advance(nan), etl::exception); + } + #endif +#endif } } // namespace