Aria provides ObservableList<T> as the mutable source and a family of derived list views that transform, filter, sort, and paginate — all reactive, all change-propagating.
Include: #include "aria/observable_list.hpp" and derived views from #include "aria/derived/filtered_list.hpp", etc.
ObservableList<T>
Thread-safe observable collection of std::shared_ptr<T>. Fires granular ListChange<T> events on every mutation.
Create and Populate
auto t1 = std::make_shared<Task>("Write docs");
tasks.append(t1);
auto t2 = std::make_shared<Task>("Ship release");
tasks.append(t2);
Observable sequence of owning element handles.
Definition observable_list.hpp:40
Mutations
tasks.
insert(0, std::make_shared<Task>(
"Urgent"));
tasks.replace(0, std::make_shared<Task>("Updated"));
void remove_at(std::size_t index)
Definition observable_list.hpp:165
void clear()
Definition observable_list.hpp:322
void move(std::size_t from, std::size_t to)
Definition observable_list.hpp:305
void insert(std::size_t index, std::shared_ptr< T > item)
Definition observable_list.hpp:126
Range Operations
std::vector<std::shared_ptr<Task>> batch = {};
tasks.
remove_all([](
const std::shared_ptr<Task>& t) {
return t->done;
});
std::size_t remove_all(Pred &&predicate)
Definition observable_list.hpp:237
std::size_t size() const
Definition observable_list.hpp:66
void insert_range(std::size_t index, InputIt first, InputIt last)
One O(n + k) insertion and k owning Insert events, in forward order.
Definition observable_list.hpp:142
void remove_range(std::size_t index, std::size_t count)
Removes in forward event order, each at the same replay pivot.
Definition observable_list.hpp:182
Syncing With a Fresh Snapshot (reconcile)
The mutators above are imperative — you name the operation. But when data arrives from a server you usually get a whole new array with no indication of what changed. reconcile works out the difference for you:
struct ById {
int operator()(const Task& t) const noexcept { return t.id; }
};
std::vector<std::shared_ptr<Task>> fresh = fetch_tasks();
std::size_t reconcile(std::vector< std::shared_ptr< T > > next, KeyFn key_of={})
Reconcile by unique key.
Definition observable_list.hpp:344
For unambiguous keys, this emits incremental Insert / Remove / Replace / Move events. Ambiguous duplicate keys use Reset; the algorithm does not promise the globally shortest edit stream. That distinction matters: on Reset observers must discard their mirror, so the view loses selection, scroll position, expansion state and row animations. A poll loop built on clear() + insert_range throws all of that away on every tick, even when nothing actually changed. Selection also clears itself on Reset, so a refresh would silently drop whatever the user had selected.
Pass a real key_of whenever the source allocates new objects for the same logical rows; the default identity is the object's address, which is only useful if you reuse handles. Reconciling an already-matching list emits nothing and returns 0.
Wrap the call in reactive::batch if downstream Computed values should recompute once at the end rather than per event.
Full semantics: docs/reference/list-diff-contract.md D-14.
Observe Changes
std::cout <<
"Inserted at " << change.
index <<
"\n";
break;
std::cout <<
"Removed at " << change.
index <<
"\n";
break;
std::cout <<
"Replaced at " << change.
index <<
"\n";
break;
<<
" to " << change.
index <<
"\n";
break;
std::cout << "List cleared\n";
break;
std::cout <<
"Item changed at " << change.
index <<
"\n";
break;
}
});
@ Replace
Definition list_change.hpp:10
@ Remove
Definition list_change.hpp:10
@ Reset
Definition list_change.hpp:10
@ Move
Definition list_change.hpp:10
@ ItemChanged
Definition list_change.hpp:10
@ Insert
Definition list_change.hpp:10
An owning event in a sequential list edit stream.
Definition list_change.hpp:16
std::size_t index
Definition list_change.hpp:19
std::size_t from_index
Definition list_change.hpp:21
ListChangeKind kind
Definition list_change.hpp:18
Read
std::size_t n = tasks.
size();
bool empty = tasks.
empty();
std::shared_ptr< T > at(std::size_t index) const
Definition observable_list.hpp:68
std::size_t index_of(const T *item) const
Definition observable_list.hpp:391
bool empty() const
Definition observable_list.hpp:67
ListChangeKind Reference
| Kind | Index | Item | From Index | Meaning |
| Insert | insertion point | inserted item | — | One item added |
| Remove | slot before removal | removed item | — | One item removed |
| Replace | position | new item | — | Item swapped |
| Move | destination | moved item | source | Item relocated |
| Reset | 0 | nullptr | — | Entire list cleared |
| ItemChanged | current position | changed item | — | Item's own on_changed fired |
Derived Views
Derived views sit on top of an ObservableList (or another derived view) and transform the stream. They are themselves ListSource implementations — you can chain them.
FilteredList
Show only items matching a predicate:
[](const std::shared_ptr<Task>& t) { return !t->done; }
};
Definition filtered_list.hpp:99
When all_tasks mutates, incomplete recalculates and emits its own ListChange events.
SortedList
Maintain items in sorted order:
[](const std::shared_ptr<Task>& a, const std::shared_ptr<Task>& b) {
return a->priority > b->priority;
}
};
Definition sorted_list.hpp:91
MappedList
Transform each item into a different type:
[](const std::shared_ptr<Task>& t) -> std::shared_ptr<std::string> {
return std::make_shared<std::string>(t->title);
}
};
Definition mapped_list.hpp:76
DistinctList
Remove duplicates by key:
[](const std::shared_ptr<Task>& t) { return t->id; }
};
Definition distinct_list.hpp:71
PagedList
Virtualize a large list into pages:
paged.page_index().set(0);
paged.page_count();
Definition paged_list.hpp:58
Re-windowing emits incremental Insert, Remove, and Move changes. Moving an existing row preserves its identity in downstream derived lists and adapters; changing the page does not require a full model reset.
GroupedList
Group items by a key function:
[](const std::shared_ptr<Task>& t) { return t->category; }
};
Definition grouped_list.hpp:93
Chaining
Derived views compose — filter first, then sort:
[](const auto& t) { return !t->done; }};
[](const auto& a, const auto& b) { return a->priority > b->priority; }};
std::shared_ptr< SortedList< T, Source > > sorted(std::shared_ptr< Source > source, Comparator comparator)
Definition sorted_list.hpp:618
Mutations to source propagate through active → sorted automatically.
Quick Reference
| Type | Input | Transform | Output Events |
| ObservableList<T> | Direct mutations | — | Insert/Remove/Replace/Move/Reset/ItemChanged |
| FilteredList<T> | Any ListSource<T> | Predicate filter | Same kinds, subset |
| SortedList<T> | Any ListSource<T> | Comparator sort | Same kinds, reordered |
| MappedList<U> | Any ListSource<T> | T → U transform | Same kinds, mapped items |
| DistinctList<T> | Any ListSource<T> | Deduplicate by key | Same kinds, deduplicated |
| PagedList<T> | Any ListSource<T> | Window by page | Same kinds, windowed |
| GroupedList<T> | Any ListSource<T> | Group by key | Group-aware events |
Composing Derived Views
Every derived view is itself a valid source for another, so views chain. Use the factory helpers — they deduce the source type, which otherwise has to be written out in full (SortedList<Row, FilteredList<Row>> and worse):
auto evens =
aria::filtered(tasks, [](
const Task& t) {
return !t.done; });
auto asc =
aria::sorted(unique, [](
const Task& a,
const Task& b) {
return a.due < b.due;
});
std::shared_ptr< PagedList< T, Source > > paged(std::shared_ptr< Source > source, std::size_t page_size, std::size_t page_index=0)
Definition paged_list.hpp:351
std::shared_ptr< FilteredList< T, Source > > filtered(std::shared_ptr< Source > source, Predicate predicate)
Definition filtered_list.hpp:588
std::shared_ptr< DistinctList< T, Key, Source > > distinct(std::shared_ptr< Source > source, KeyFn key_of)
Definition distinct_list.hpp:612
Mutating tasks propagates all the way to page, each stage applying its own transformation. Lifetime is transitive too: each stage holds a strong reference to its source, so keeping page keeps the whole pipeline alive and the intermediate handles can go out of scope.
mapped<Target>() changes the element type mid-pipeline:
return std::make_shared<RowViewModel>(t);
});
std::shared_ptr< MappedList< Source, Target, SourceList > > mapped(std::shared_ptr< SourceList > source, MapFn mapper, bool remap_on_change=false)
Definition mapped_list.hpp:299
Full semantics: docs/reference/list-diff-contract.md D-41.
See Also
- Reactive Core → — Property and Computed that power list observations
- List Diff Contract → — authoritative event-sequence specification
- Adapters → — platform-specific list binding (Qt QAbstractListModel, AppKit NSTableView, etc.)