Merge branch 'development' into feature/to_string-float-uses-double-math

This commit is contained in:
John Wellbelove 2026-09-29 13:16:21 +02:00 committed by GitHub
commit 763f6fead7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 2010 additions and 318 deletions

View File

@ -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

View File

@ -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.

View File

@ -0,0 +1,6 @@
---
title: "Trees"
weight: 100
---
Tree like containers.

View File

@ -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 <typename TValue, size_t ID_ = 0>
etl::intrusive_avl_tree
```
`TValue` is the type that contains the actual values. It must derive from
`etl::intrusive_avl_tree_base<ID_>::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<ID_>` 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<Item, 0> tree0;
etl::intrusive_avl_tree<Item, 1> 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<value_type>::cmp`
can be used directly if `value_type` defines `<`.
## Member types
```cpp
link_type (nested, inherited from etl::intrusive_avl_tree_base<ID_>)
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<TValue, ID_>()
```
**Description**
Default constructor. Constructs an empty tree.
---
```cpp
template <typename TIterator, typename TBinaryCompare>
etl::intrusive_avl_tree<TValue, ID_>(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<value_type>::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 <typename TCompare>
iterator find(TCompare comp)
template <typename TCompare>
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 <typename TCompare>
iterator lower_bound(TCompare comp)
template <typename TCompare>
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 <typename TCompare>
iterator upper_bound(TCompare comp)
template <typename TCompare>
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 <typename TCompare, typename TFactory>
etl::pair<iterator, bool> 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 <typename Visitor>
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 <typename Visitor>
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 <typename Visitor>
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
```

View File

@ -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.

View File

@ -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.

View File

@ -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 <cstdint>
#include <iostream>
#include <vector>
#include <algorithm>
// 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<SensorTask> 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<SensorTask> 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
```

View File

@ -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<T>` and `referenced_clamped_value<T>` are
available in C++11 and later.
```cpp
// C++11 and later
@ -48,6 +49,20 @@ etl::clamped_value<float, -1.5f, 2.5f> float_ct(0.5f);
#endif
```
In C++11 and later, multiple values can share one immutable runtime range:
```cpp
const etl::clamped_value_range<float> range(-1.5f, 2.5f);
etl::referenced_clamped_value<float> value1(range, 0.5f);
etl::referenced_clamped_value<float> value2(range, 1.5f);
```
`referenced_clamped_value<T>` stores an
`etl::reference_wrapper<const clamped_value_range<T>>`. 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<T>` also defines `range_type` as
`clamped_value_range<T>`.
## 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<T> range(min, max);
referenced_clamped_value<T> value(range);
referenced_clamped_value<T> 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<T>` 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<T>` 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<T>` additionally provides:
```cpp
ETL_NODISCARD const range_type& range() const noexcept;
```
This returns the exact `clamped_value_range<T>` 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<T>` 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<T>` 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<T>` and
when assigning or advancing a `referenced_clamped_value<T>`.
## Shared Referenced Ranges
`clamped_value_range<T>` and `referenced_clamped_value<T>` are available in
C++11 and later. They let independent values share immutable runtime bounds.
### `clamped_value_range<T>`
```cpp
template <typename T>
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<T>`
```cpp
template <typename T>
class referenced_clamped_value;
```
The class stores a current value and an
`etl::reference_wrapper<const clamped_value_range<T>>`. 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<T>`.
#### 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<T>`. 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<T>`.

View File

@ -297,6 +297,8 @@ template <const size_t ID_>
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_

View File

@ -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;
}

View File

@ -0,0 +1,3 @@
<span class="complexity {{ .Get 0 }}">
𝐎({{ .Get 1 }})
</span>

View File

@ -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 <typename T>
class clamped_value_range
{
public:
typedef typename private_clamped_value::traits<T>::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 <typename T>
class referenced_clamped_value
{
public:
/// The referenced range type.
typedef clamped_value_range<T> range_type;
/// The type used to advance or subtract from the stored value.
typedef typename private_clamped_value::traits<T>::difference_type difference_type;
/// Numeric limits for the stored value type.
typedef typename private_clamped_value::traits<T>::limits_type limits_type;
/// Numeric limits for difference_type.
typedef typename private_clamped_value::traits<T>::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<const range_type> value_range;
};
#endif
} // namespace etl
#endif

View File

@ -37,3 +37,13 @@ etl::clamped_value<double> clamped_double_run_time;
#if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE
etl::clamped_value<float, -1.0f, 1.0f> clamped_float_compile_time;
#endif
#if ETL_USING_CPP11
const etl::clamped_value_range<int> referenced_clamped_range(0, 10);
etl::referenced_clamped_value<int> referenced_clamped_int(referenced_clamped_range);
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE
const etl::clamped_value_range<float> referenced_clamped_float_range(-1.0f, 1.0f);
etl::referenced_clamped_value<float> referenced_clamped_float(referenced_clamped_float_range);
#endif
#endif

View File

@ -531,5 +531,157 @@ namespace
}
#endif
#endif
#if ETL_USING_CPP11
//*************************************************************************
TEST(test_referenced_clamped_value_uses_shared_range)
{
const etl::clamped_value_range<int> range(2, 7);
etl::referenced_clamped_value<int> value1(range, 1);
etl::referenced_clamped_value<int> 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<int> range(-2, 3);
etl::referenced_clamped_value<int> 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<int> range(2, 7);
etl::referenced_clamped_value<int> value(range);
CHECK_EQUAL(2, value.get());
value.set(4);
CHECK_EQUAL(4, value.get());
etl::referenced_clamped_value<int> 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<int> default_range;
const etl::clamped_value_range<int> range(0, 10);
etl::referenced_clamped_value<int> value1(range, 3);
etl::referenced_clamped_value<int> value2(range, 4);
const int converted = value1;
CHECK_EQUAL(etl::numeric_limits<int>::lowest(), default_range.min());
CHECK_EQUAL(etl::numeric_limits<int>::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<int> range1(0, 10);
const etl::clamped_value_range<int> range2(20, 30);
etl::referenced_clamped_value<int> value1(range1, 4);
etl::referenced_clamped_value<int> value2(range2, 24);
etl::referenced_clamped_value<int> copy(value1);
CHECK_EQUAL(&range1, &copy.range());
CHECK_EQUAL(4, copy.get());
copy = value2;
CHECK_EQUAL(&range2, &copy.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<int>(7, 2)), etl::exception);
}
//*************************************************************************
TEST(test_referenced_clamped_value_rejects_temporary_range)
{
typedef etl::clamped_value_range<int> range_type;
typedef etl::referenced_clamped_value<int> value_type;
CHECK_FALSE((etl::is_constructible<value_type, range_type&&>::value));
CHECK_FALSE((etl::is_constructible<value_type, range_type&&, int>::value));
}
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE
//*************************************************************************
TEST(test_referenced_floating_clamped_value)
{
const etl::clamped_value_range<float> range(-1.5F, 2.5F);
etl::referenced_clamped_value<float> 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<float>::quiet_NaN();
CHECK_THROW((etl::clamped_value_range<float>(nan, 1.0F)), etl::exception);
const etl::clamped_value_range<float> range(-1.0F, 1.0F);
CHECK_THROW((etl::referenced_clamped_value<float>(range, nan)), etl::exception);
etl::referenced_clamped_value<float> value(range, 0.0F);
CHECK_THROW(value = nan, etl::exception);
CHECK_THROW(value.advance(nan), etl::exception);
}
#endif
#endif
}
} // namespace