Build
Aria supports C++23; C++20 remains the default and minimum. The default build:
cmake -B build/flavors/release -DCMAKE_BUILD_TYPE=Release
cmake --build build/flavors/release -j
ctest --test-dir build/flavors/release --output-on-failure
To select C++23 explicitly, add -DCMAKE_CXX_STANDARD=23 when configuring:
cmake -B build/flavors/cxx23 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=23
cmake --build build/flavors/cxx23 -j
ctest --test-dir build/flavors/cxx23 --no-tests=error --output-on-failure
The repository includes doctest, so the normal test build uses the bundled header. If that vendored header is absent, CMake fetches the fallback test dependency through cmake/CPM.cmake.
Use Aria in an application
Install the build into a prefix of your choice, for example from the Aria repository root:
cmake --install build/flavors/release --prefix "$PWD/aria-install"
In a separate application directory, save the next example as main.cpp and create this CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(greeting LANGUAGES CXX)
find_package(aria 2.0 CONFIG REQUIRED COMPONENTS core)
add_executable(greeting main.cpp)
target_link_libraries(greeting PRIVATE aria::core)
Configure the application with the absolute path to that installation:
cmake -S . -B build -DCMAKE_PREFIX_PATH=/absolute/path/to/Aria/aria-install
cmake --build build
The imported target supplies the C++20 minimum. You can add -DCMAKE_CXX_STANDARD=23 to the application's configure command to select C++23. The aria/aria.hpp umbrella contains the core API; async and platform adapters have their own headers and CMake targets.
Hello, Property
#include <functional>
#include <iostream>
#include <string>
int main() {
auto sub = name.
bind([](
const std::string& v) {
std::cout << "Hello, " << v << "!\n";
});
name = "Alice";
name = "Bob";
}
Definition property.hpp:103
::aria::Subscription bind(std::function< void(const T &)> fn)
Fire once with the current value, then on every subsequent change.
Definition property.hpp:210
bind() invokes the callback once with the current value and again on every change. The returned Subscription is RAII — let it drop out of scope to disconnect.
The next four snippets each replace the body of main() in this program; keep its includes and using namespace aria; declaration.
Computed (derived) properties
std::cout << sum.
get() <<
"\n";
a = 10;
std::cout << sum.
get() <<
"\n";
Definition computed.hpp:86
T get() const
Return the cached value, ensuring it is up to date.
Definition computed.hpp:135
T get() const
Auto-tracked read.
Definition property.hpp:138
Commands with CanExecute
[]{ std::cout << "Signed out\n"; },
[&]{
return logged_in.
get(); }
);
logged_in = true;
logout.execute();
Definition command.hpp:154
void execute()
Definition command.hpp:232
A button can be bound to the command via BindingEngine::bind_command() — the button's enabled state automatically tracks can_execute.
ObservableList
struct Todo {
[[nodiscard]]
Subscription on_changed(std::function<
void(
const Todo&)> fn) {
return done.
on_changed([
this, fn](
bool) { fn(*
this); });
}
};
});
t->done = true;
Observable sequence of owning element handles.
Definition observable_list.hpp:40
std::shared_ptr< T > emplace_back(Args &&... args)
Definition observable_list.hpp:120
void remove_at(std::size_t index)
Definition observable_list.hpp:165
RAII handle to a single subscription.
Definition subscription.hpp:44
::aria::Subscription on_changed(std::function< void(const T &)> fn)
Run fn(new_value) every time the value changes.
Definition property.hpp:200
@ Remove
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
ListChangeKind kind
Definition list_change.hpp:18
The ItemChanged notification is automatic if T provides an on_changed member that returns a Subscription.
Validation
v.must([](auto& s){ return !s.empty(); }, "Email is required")
.must([](auto& s){ return s.find('@') != std::string::npos; }, "Email must contain @");
const auto& state = v.state().get();
if (!state.valid) {
for (const auto& error : state.errors) {
std::cerr << error.message << "\n";
}
}
Definition validator.hpp:135
Async work with coroutines
For this complete console program, change the CMake component to async and link aria::async. A future carries the result back to the console thread, while CoroutineScope owns the running task.
#include <exception>
#include <future>
#include <iostream>
#include <utility>
#include <vector>
int sum = 0;
for (auto x : xs) sum += x;
co_return sum;
}
std::promise<int> completion) {
try {
const int total = co_await compute_total(worker, std::move(xs));
completion.set_value(total);
} catch (...) {
completion.set_exception(std::current_exception());
}
}
int main() {
std::promise<int> completion;
auto result = completion.get_future();
scope.
launch_simple(complete_total(pool, {1, 2, 3, 4}, std::move(completion)));
int status = 0;
try {
std::cout << result.get() << "\n";
} catch (const std::exception& error) {
std::cerr << error.what() << "\n";
status = 1;
}
return status;
}
bool cancel_and_join(std::chrono::milliseconds timeout=std::chrono::milliseconds{5000}) noexcept
Synchronously: cancel + wait for all in-flight coroutines to finish, with a bounded timeout (default ...
Definition scope.hpp:138
void launch_simple(Task< void > task)
Convenience overload for a fully-formed Task<void> whose body already captures the cancellation token...
Definition scope.hpp:287
Abstract executor interface — schedules a callable to run "somewhere".
Definition executor.hpp:36
Thread pool executor.
Definition executor.hpp:71
Definition async_command.hpp:118
auto schedule_on(IExecutor &exec)
Schedule a coroutine to resume on the given executor.
Definition executor.hpp:396
The promise and input vector are owned by coroutine parameters; the worker reference remains valid until the tasks finish. blocking_get() only supports tasks that finish during a single synchronous resume, so it cannot wait for this thread-pool operation.
In a GUI application, keep the UI event loop running and dispatch view-model writes back to its owner thread. Do not block that thread waiting for work that needs the same event loop. See the async guide and Qt guide for executor and dispatcher integration.
Wiring a real ViewModel
Enable the Qt adapter when building Aria (-DARIA_BUILD_QT6=ON), then rebuild and reinstall it. CMake must be able to find your Qt 6 Core and Widgets installation. For the application, use find_package(aria 2.0 CONFIG REQUIRED COMPONENTS qt6) and link aria::qt6 in the CMake file above.
This complete Qt Widgets program binds a text field, a derived label, and a reset button:
#include <QApplication>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <memory>
#include <string>
public:
[
this]{
return "Hello, " + name.
get() +
"!"; }
};
Command<> reset{[this]{ name = "World"; }};
};
int main(int argc, char** argv) {
QApplication app(argc, argv);
GreetingViewModel vm;
QWidget window;
auto* layout = new QVBoxLayout(&window);
auto* name_edit = new QLineEdit(&window);
auto* greeting_label = new QLabel(&window);
auto* reset_button = new QPushButton("Reset", &window);
layout->addWidget(name_edit);
layout->addWidget(greeting_label);
layout->addWidget(reset_button);
auto adapter = std::make_shared<adapters::qt6::QtAdapter>();
engine.bind_text(vm.name, adapter->view_for(name_edit));
engine.bind_text_oneway(vm.greeting, adapter->view_for(greeting_label));
engine.bind_command(vm.reset, adapter->view_for(reset_button));
window.show();
return app.exec();
}
BindingEngine: connects ViewModel properties to platform views via an adapter.
Definition binding_engine.hpp:103
Base class for view models.
Definition view_model.hpp:32
BindingEngine binds an IView&. QtAdapter::view_for() supplies and owns the wrapper for each native widget; the returned reference remains valid until that widget or the adapter is destroyed. Qt owns the child widgets, and the declaration order above destroys the engine before the adapter, widgets, and view-model.
This example performs all updates on the Qt UI thread. Other implemented adapters can reuse the view-model with their own native view wiring. SwiftUI integration remains conditional work in the roadmap.
Next steps
- Read docs/architecture.md for the layering rationale.
- Read the Qt guide for more widget bindings and UI dispatch.
- Browse tests/acceptance/ for executable framework contracts and the focused snippets in these guides for individual concepts.
- With ARIA_BUILD_BENCHMARK=ON, run a benchmark such as ./build/flavors/release/bin/aria_bench_command (multi-config generators add a configuration directory such as bin/Release/).