You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The goal of this project is to provide C++20 and C++23 developers with convenient and minimal cross-platform C++ utility libraries.
All of the modules are heavily performance oriented and provide the least amount of overhead possible.
Supported Platforms
Platform
Compilers
Notes
Windows
MSVC++ 2019, 2022, 2026
x64
Ubuntu Linux
clang++ 14–20, g++ 11–15
x86_64, ASan/TSan/clang-tidy CI
Android
NDK r25b–r29 (clang++ 14–20)
arm64-v8a, armeabi-v7a
iOS / macOS
Apple Clang
arm64, x86_64
AArch64 Embedded
g++ 11–15
i.MX8M+, Xilinx Zynq UltraScale+, Ambarella CV25
MIPSEL
g++ 11
Cross-compiled, no tests
Building
ReCpp uses mama build system with CMake.
You can either build it as a regular CMake project or use the provided mamafile.py for more convenient builds and testing.
cmake -B build -S .
cmake --build build
# Build with tests (defaults to GCC on Linux or MSVC on Windows)
mama build with_tests
# Build debug with tests and run with gdb
mama build debug test# Run specific tests with clang and sanitizers
mama clang clang-tidy asan build test="test_strview"# Run tests until failure with GCC and ThreadSanitizer
mama gcc tsan build test test_until_failure
An extremely fast and powerful set of string-parsing utilities. Includes fast tokenization, blazing fast number parsers, extremely fast number formatters, and basic string search functions and utilities, all extremely well optimized.
Works on UTF-8 strings. For unicode-aware parsing, use rpp::ustrview and define RPP_ENABLE_UNICODE=1 before including.
The string view header also provides utility parsers such as line_parser, keyval_parser and bracket_parser.
rpp::strview text = buffer;
rpp::strview line;
while (text.next(line, '\n'))
{
line.trim();
if (line.empty() || line.starts_with('#'))
continue;
if (line.starts_withi("numObJecTS")) // ignore case
{
objects.reserve(line.to_int());
}
elseif (line.starts_with("object")) // "object scene_root 192 768"
{
line.skip('');
rpp::strview name = line.next('');
int posX = line.next_int();
int posY = line.next_int();
objects.push_back(scene::object{name.to_string(), posX, posY});
}
}
// split_first / split_second for quick one-shot splits
rpp::strview url = "https://example.com/api/v2/users?active=true";
rpp::strview protocol = url.split_first("://"); // "https"
rpp::strview query = url.split_second('?'); // "active=true"// extract file extension from a path
rpp::strview path = "/home/user/documents/report.final.pdf";
rpp::strview ext = path.split_second('.'); // gets everything after first '.' --> "final.pdf"// for the last extension, use rfind:if (constchar* dot = path.rfind('.'))
rpp::strview lastExt = { dot + 1, path.end() }; // "pdf"
Example: CSV Row Parsing with next()
// next() tokenizes and advances the strview — perfect for CSV-style data
rpp::strview row = "Alice,30,Engineering,true";
rpp::strview name = row.next(','); // "Alice"int age = row.next(',').to_int(); // 30
rpp::strview dept = row.next(','); // "Engineering"bool active = row.next(',').to_bool(); // true
Example: Extracting Numbers from Mixed Text
// next_int / next_float skip non-numeric chars and parse the next number
rpp::strview mixed = "x=42 y=-7.5 z=100";
int x = mixed.next_int(); // 42float y = mixed.next_float(); // -7.5int z = mixed.next_int(); // 100
Example: Decompose Structured Data
// decompose auto-parses into typed variables in a single call
rpp::strview input = "user@email.com;27;3486.37;true"_sv;
rpp::strview email;
int age;
float coins;
bool unlocked;
input.decompose(';', email, age, coins, unlocked);
// email="user@email.com" age=27 coins=3486.37 unlocked=true
rpp::line_parser parser { fileContents };
rpp::strview line;
while (parser.read_line(line)) // reads next line, trimmed of \r and \n
{
line.trim(); // trim any space or tab whitespace from the lineif (line.empty() || line.starts_with('#'))
continue; // skip blanks and commentsLogInfo("%s", line); // debug macros can print strviews safely via rpp::__wrap<strview> which calls line.to_cstr() under the hood
}
Example: Using keyval_parser
constchar* config = "# database settings\n"" host = localhost \n"" port = 5432 \n"" name = mydb \n";
rpp::keyval_parser parser { config };
rpp::strview key, value;
// reads next key=value pair, each key and value are trimmed of whitespace, comment # and blank lines skippedwhile (parser.read_next(key, value))
{
// key="host" value="localhost", key="port" value="5432", etc.if (key == "port")
settings.port = value.to_int();
}
Example: strview as HashMap Key
// strview is hashable — use it directly in unordered_map
std::unordered_map<rpp::strview, int> counts;
rpp::strview words = "the quick brown fox jumps over the lazy dog";
rpp::strview word;
while (words.next(word, ''))
counts[word]++;
// counts["the"] == 2
Example: Contains, Starts/Ends With
rpp::strview filename = "screenshot_2026-03-13.png";
// contains - check for chars or substringsif (filename.contains('.')) // true — has a dotif (filename.contains("screenshot")) // true — has substring// contains_any - check for any of the given charsif (filename.contains_any("!@#$")) // false — no special chars// starts_with / ends_withif (filename.starts_with("screenshot")) // trueif (filename.ends_with(".png")) // true// case-insensitive variants
rpp::strview cmd = "GET /index.html HTTP/1.1";
if (cmd.starts_withi("get")) // true — ignores caseif (cmd.ends_withi("http/1.1")) // true
Example: Equals and Case-Insensitive Comparison
rpp::strview ext = "PNG";
// exact equality via operator== or equals()if (ext == "PNG") // trueif (ext == "png") // falseif (ext.equals("PNG")) // true// case-insensitive equalityif (ext.equalsi("png")) // trueif (ext.equalsi("Png")) // true// works with std::string too
std::string expected = "PNG";
if (ext == expected) // trueif (ext.equalsi("pNg")) // true// compare() for ordering (returns <0, 0, >0 like strcmp)
rpp::strview a = "apple", b = "banana";
if (a < b) // true — lexicographic ordering via compare()
ustrview Class
struct ustrview — UTF-16 (char16_t) counterpart of strview. Requires RPP_ENABLE_UNICODE=1 to be defined before including the header. Provides the same non-owning, non-null-terminated string view semantics as strview, but operates on char16_t data. Implicitly converts from std::u16string and std::u16string_view. On MSVC, also accepts const wchar_t*.
#defineRPP_ENABLE_UNICODE1
#include<rpp/strview.h>usingnamespacerpp::literals;// construct from char16_t literal or std::u16string
rpp::ustrview name = u"日本語テキスト";
rpp::ustrview greeting = u"こんにちは世界"_sv;
// tokenize CSV-style UTF-16 data
rpp::ustrview csv = u"名前,年齢,都市";
rpp::ustrview token;
while (csv.next(token, u','))
{
std::u16string value = token.to_string();
}
// trimming and comparison
rpp::ustrview padded = u" hello ";
padded.trim();
if (padded.starts_with(u"hel"))
LogInfo("starts with hel");
// convert to UTF-8 std::string and back to UTF-16
std::string utf8 = rpp::to_string(name);
rpp::ustring utf16 = rpp::to_ustring(utf8); // rpp::ustring is alias for std::u16string
rpp/sprint.h
Fast string building and type-safe formatting. string_buffer is an always-null-terminated string builder that auto-converts most types.
Example using file_io for reading and writing files:
#include<rpp/file_io.h>voidfileio_read_sample(rpp::strview filename = "README.md"_sv)
{
if (rpp::file f = { filename, rpp::file::READONLY }) // open the file explicitly, or use static file::read_all() util
{
// reads all data in the most efficient way
rpp::load_buffer data = f.read_all();
// use the data as a binary blobfor (char& ch : data) { }
}
}
voidfileio_writeall_sample(rpp::strview filename = "test.txt"_sv)
{
std::string someText = "/**\n * A simple self-expanding buffer\n */\nstruct";
// write a new file with the contents of someTextrpp::file::write_new(filename, someText.data(), someText.size());
// or just write it as a string rpp::file::write_new(filename, someText);
}
voidfileio_modify_sample(rpp::strview filename = "test.txt"_sv)
{
if (rpp::file f = { filename, rpp::file::READWRITE })
{
// append a line to the file
f.seek(0, rpp::file::END);
f.writeln("This line was appended to the file.");
// read the first 100 byteschar buffer[101] = {};
f.seek(0, rpp::file::BEGIN);
f.read(buffer, 100);
LogInfo("First 100 bytes:\n%s", buffer);
// truncate the last 50 bytes from the end
f.truncate_end(50);
// truncate the first 10 bytes from the beginning
f.truncate_front(10);
}
}
Example: Quick One-Liners
#include<rpp/file_io.h>// read entire file into a managed buffer (auto-freed)
rpp::load_buffer buf = rpp::file::read_all("config.bin");
if (buf) process(buf.data(), buf.size());
// read entire file as std::string
std::string text = rpp::file::read_all_text("README.md");
// create/overwrite a file in one callrpp::file::write_new("output.txt", "Hello, world!");
// write binary data
std::vector<float> data = {1.0f, 2.0f, 3.0f};
rpp::file::write_new("data.bin", data);
Example: Appending with writeln
// APPEND mode opens or creates the file for appendingif (rpp::file log = { "app.log", rpp::file::APPEND })
{
log.writeln("Application started");
log.writef("[%s] event=%s code=%d\n", timestamp, "click", 42);
// multi-arg writeln auto-separates with spaces (like Python print)
log.writeln("user:", username, "action:", "login", "ip:", clientIp);
// writes: "user: alice action: login ip: 192.168.1.1\n"
}
Example: Line-by-Line File Parsing
// buffer_line_parser loads the file and parses linesif (auto parser = rpp::buffer_line_parser::from_file("data.csv"))
{
rpp::strview line;
while (parser.read_line(line))
{
if (line.empty() || line.starts_with('#'))
continue;
rpp::strview name = line.next(',');
int value = line.next(',').to_int();
LogInfo("%s = %d\n", name, value);
}
}
Example: Using load_buffer with strview
// load_buffer integrates seamlessly with strview for parsingif (rpp::load_buffer buf = rpp::file::read_all("scene.txt"))
{
rpp::strview content = buf.view();
rpp::strview header = content.next('\n'); // first lineint objectCount = content.next('\n').to_int(); // second linefor (int i = 0; i < objectCount; ++i)
{
rpp::strview objLine = content.next('\n');
// parse each object line...
}
}
rpp/paths.h
Cross-platform path manipulation, directory listing, and filesystem utilities.
// list all .h files in src/ (non-recursive, relative paths)auto headers = rpp::list_files("src", "h");
// headers: {"strview.h", "sprint.h", "config.h", ...}// list all .cpp files recursively with full pathsauto sources = rpp::list_files("src", "cpp", rpp::dir_fullpath_recursive);
// list only directoriesauto dirs = rpp::list_dirs("packages", rpp::dir_recursive);
Example: Range-Based Directory Iterator
for (rpp::dir_entry e : rpp::dir_iterator{"src/rpp"})
{
if (e.is_file())
LogInfo("file: %s %s", e.name(), e.path()); // e.path() returns "src/rpp/{name}"elseif (e.is_dir())
LogInfo("dir: %s %s", e.name() e.full_path()); // e.full_path() returns absolute path
}
rpp/delegate.h
Fast function delegates as an optimized alternative to std::function. Supports static functions, instance member functions, lambdas, and functors with single virtual call overhead. Compared to std::function, delegate pays an upfront cost for allocation, but calls are devirtualized and completely optimized, leading to much faster invocation (ideal for hot code paths and event systems).
Fail-fast destructor: if a valid future is not awaited before destruction, calls std::terminate(). If the future was already completed, any stored exception is caught and triggers an assertion failure. This is a deliberate deviation from std::future which silently blocks in the destructor — ReCpp terminates immediately to surface programming bugs.
rpp::async_task([=] {
returnloadCachedScene(getCachePath(file));
}, [=](invalid_cache_state e) {
returnloadCachedScene(downloadAndCacheFile(file)); // recover from bad cache
}).then([=](std::shared_ptr<SceneData> sceneData) {
setCurrentScene(sceneData);
}, [=](scene_load_failed e) {
loadDefaultScene(); // recover from failed scene load
});
Example: Sequential Chaining with chain_async()
cfuture<void> tasks;
tasks.chain_async([=]{
downloadAssets(manifest);
}).chain_async([=]{
parseAssets(manifest); // runs after download completes, even if previous task throws an exception
}).chain_async([=]{
initializeRenderer(); // runs after parsing completes
});
tasks.get(); // wait for the full chain
Example: Non-blocking Result Polling with collect_ready()
auto f = rpp::async_task([=]{ returncomputeResult(data); });
// poll in a game loop without blockingint result;
while (!f.collect_ready(&result)) {
renderLoadingScreen();
}
useResult(result);
Example: detach() for Fire-and-Forget
auto f = rpp::async_task([=]{
sendAnalyticsEvent(event);
});
if (f.wait_for(rpp::millis(100)) == std::future_status::timeout) {
LogWarning("Analytics event is taking too long to send, abandoning to unblock UI");
f.detach(); // don't care about the result, avoid terminate on destruction
}
Example: C++20 Coroutines
usingnamespacerpp::coro_operators;
rpp::cfuture<void> downloadAndUnzipDataAsync(std::string url)
{
std::string zipPath = co_await [=]{ returndownloadZipFile(url); }; // spawns a new task to exec lambda on thread pool
std::string extractedDir = co_await [=]{ returnextractContents(zipPath); };
co_awaitcallOnUIThread([=]{ jobComplete(extractedDir); });
co_return;
}
Lightweight non-owning delegate for blocking call contexts
Example: parallel_for
#include<rpp/thread_pool.h>
std::vector<Image> images = loadImages();
// process images in parallel using the global thread pool// callback receives [start, end) range — efficient for lightweight per-item workrpp::parallel_for(0, (int)images.size(), 0, [&](int start, int end) {
for (int i = start; i < end; ++i)
processImage(images[i]);
});
// blocks until all tasks complete// with explicit max_range_size=1 for heavy per-item tasksrpp::parallel_for(0, (int)images.size(), 1, [&](int start, int end) {
for (int i = start; i < end; ++i)
renderScene(images[i]); // each item gets its own thread
});
Example: parallel_foreach
// parallel_foreach is a convenience wrapper — one callback per item
std::vector<std::string> files = rpp::list_files("textures", "png", rpp::dir_fullpath_recursive);
rpp::parallel_foreach(files, [](std::string& path) {
compressTexture(path);
});
// blocks until all items are processed
Example: parallel_task (Fire-and-Forget)
// launch an async task on the global pool — returns immediately
rpp::pool_task_handle handle = rpp::parallel_task([=] {
uploadAnalytics(report);
});
// optionally wait for completion
handle.wait();
Example: Using a Dedicated Thread Pool
// create a pool with limited parallelism for IO-bound work
rpp::thread_pool ioPool;
ioPool.set_max_parallelism(4);
ioPool.parallel_for(0, (int)urls.size(), 1, [&](int start, int end) {
for (int i = start; i < end; ++i)
downloadFile(urls[i]);
});
Template constraint placeholder for SyncableType concept
Example: Basic Mutex and Spin Lock
#include<rpp/mutex.h>
rpp::mutex mtx;
// standard lock_guard usage
{
std::lock_guard<rpp::mutex> lock{mtx};
sharedData.push_back(value);
}
// spin_lock: spins briefly before blocking — much faster under contention
{
auto lock = rpp::spin_lock(mtx);
sharedData.push_back(value);
}
// spin_lock_for: spin with a timeout, check if lock was acquired
{
auto lock = rpp::spin_lock_for(mtx, rpp::Duration::from_millis(50));
if (lock.owns_lock())
sharedData.push_back(value);
}
Example: synchronized<T> Wrapper
#include<rpp/mutex.h>// wrap any type for automatic thread-safe access
rpp::synchronized<std::vector<int>> items;
// arrow operator returns a synchronize_guard that acquires a temporary lock for safe access:
items->push_back(10); // locks, pushes, unlocks immediately after the statement// operator* returns a synchronize_guard that locks on construction// and unlocks on destruction — like a combined lock_guard + accessor
{
auto guard = *items;
guard->push_back(42);
guard->push_back(99);
} // mutex released here// direct assignment through the guard
*items = std::vector<int>{1, 2, 3};
// read through the guard
{
for (int val : *items)
LogInfo("item: %d", val);
}
rpp/semaphore.h
Counting semaphore and lightweight notification flags.
#include<rpp/semaphore.h>
rpp::semaphore workReady;
std::vector<Task> taskQueue;
// producer thread
taskQueue.push_back(newTask);
workReady.notify(); // wake one consumer// consumer thread
workReady.wait(); // blocks until notified, then decrements count
Task t = taskQueue.pop_back();
process(t);
// non-blocking checkif (workReady.try_wait())
handleImmediateWork();
// wait with timeout, uses rpp::condition_variable internally for more accurate waitsif (workReady.wait(rpp::Duration::from_millis(100)) == rpp::semaphore::notified)
handleWork();
elsehandleTimeout();
Example: semaphore_flag and semaphore_once_flag
#include<rpp/semaphore.h>// semaphore_flag: a simple set/unset signal (max count = 1)
rpp::semaphore_flag shutdownFlag;
// worker threadwhile (!shutdownFlag.try_wait())
{
doWork();
rpp::sleep_ms(100);
}
// main thread signals shutdown
shutdownFlag.notify();
// semaphore_once_flag: set once, wait() never unsets — ideal for "init done" signals
rpp::semaphore_once_flag initDone;
// init threadloadResources();
initDone.notify(); // signal that init is complete// any number of threads can wait — once set, wait() returns immediately
initDone.wait(); // does NOT consume the signal
initDone.wait(); // still returns immediately
rpp/condition_variable.h
Extended condition variable with rpp::Duration and rpp::TimePoint overloads. On Windows provides a custom implementation with spin-wait for sub-15.6ms timeouts. This is a significant upgrade for MSVC++ where std::condition_variable cannot do fine-grained waits.
Creates an INADDR_ANY TCP listener bound to a random port
Example: IP Addresses
#include<rpp/sockets.h>// construct from "ip:port" string — auto-detects IPv4 vs IPv6
rpp::ipaddress addr{"192.168.1.100:8080"};
LogInfo("addr: %s port: %d", addr.str(), addr.port()); // "192.168.1.100:8080"// construct with explicit family
rpp::ipaddress4 v4{"10.0.0.1", 5000};
rpp::ipaddress6 v6{"::1", 5000};
// listener address (any interface, specific port)
rpp::ipaddress listener{rpp::AF_IPv4, 14550};
// resolve hostname
rpp::ipaddress resolved{rpp::AF_IPv4, "example.com", 80};
LogInfo("resolved example.com to %s", resolved.str());
Example: TCP Client
// connect to a remote server (non-blocking by default, TCP_NODELAY enabled)
rpp::socket client = rpp::socket::connect_to(rpp::ipaddress4{"127.0.0.1", 8080});
if (client.bad())
{
LogError("connect failed: %s", client.last_err());
return;
}
// send data — accepts const char*, std::string, strview, vector<uint8_t>
client.send("GET / HTTP/1.0\r\nHost: localhost\r\n\r\n");
// wait for response with timeout
std::string response = client.wait_recv_str(/*millis=*/2000);
if (!response.empty())
LogInfo("response: %s", response);
// socket auto-closes on destruction
Example: TCP Server (Listen + Accept)
// create a TCP listener on port 9000
rpp::socket server = rpp::socket::listen_to(rpp::ipaddress4{9000});
if (server.bad())
{
LogError("listen failed: %s", server.last_err());
return;
}
LogInfo("listening on %s", server.str());
// accept loop — timeout=100ms for non-blocking pollingwhile (running)
{
rpp::socket client = server.accept(/*timeoutMillis=*/100);
if (client.good())
{
LogInfo("client connected from %s", client.str());
// echo server: receive and send backchar buf[1024];
int n = client.recv(buf, sizeof(buf));
if (n > 0) client.send(buf, n);
}
// client auto-closes when it goes out of scope
}
Example: UDP Send/Receive
// create a UDP socket bound to port 5000
rpp::socket udp = rpp::socket::listen_to_udp(5000);
// send a datagram to a specific address
rpp::ipaddress4 target{"192.168.1.255", 5000};
udp.sendto(target, "hello from UDP");
// receive datagrams
rpp::ipaddress from;
char buf[1024];
int n = udp.recvfrom(from, buf, sizeof(buf));
if (n > 0) LogInfo("received %d bytes from %s", n, from.str());
// convenience: wait with timeout and get as string
std::string msg = udp.wait_recvfrom_str(from, /*millis=*/1000);
rpp::socket client = rpp::socket::connect_to(rpp::ipaddress4{"10.0.0.1", 8080},
rpp::SO_NonBlock);
// poll() is faster than select() — returns true when data is availableif (client.poll(/*timeoutMillis=*/500, rpp::socket::PF_Read))
{
std::string data = client.recv_str();
process(data);
}
// poll multiple sockets at once
std::vector<rpp::socket*> sockets = {&sock1, &sock2, &sock3};
std::vector<int> ready;
int numReady = rpp::socket::poll(sockets, ready, /*timeoutMillis=*/100);
for (int idx : ready)
handleSocket(*sockets[idx]);
Example: Network Interfaces
// list all IPv4 network interfaces// WARNING: this is inherently slow on both Linux and Windows, so always cache the results !auto interfaces = rpp::ipinterface::get_interfaces(rpp::AF_IPv4);
for (auto& iface : interfaces)
LogInfo("%-10s addr=%-16s gw=%s", iface.name, iface.addr.str(), iface.gateway.str());
// get the system's main IP and broadcast addresses
std::string myIp = rpp::get_system_ip(); // e.g. "192.168.1.42"
std::string bcast = rpp::get_broadcast_ip(); // e.g. "192.168.1.255"// bind a socket to a specific network interface
rpp::socket udp = rpp::socket::listen_to_udp(5000);
udp.bind_to_interface("eth0");
rpp/binary_stream.h
Buffered binary read/write stream with abstract source interface.
#include<rpp/binary_stream.h>// binary_stream without a stream_source works as an in-memory buffer
rpp::binary_stream bs;
// typed write methods
bs.write_int32(42);
bs.write_float(3.14f);
bs.write("hello"); // writes [uint16 len][data]// read back in the same order
bs.rewind(); // reset read position to beginning
rpp::int32 id = bs.read_int32(); // 42float value = bs.read_float(); // 3.14
std::string name = bs.read_string(); // "hello"
Example: operator<< and operator>> Streaming
rpp::binary_stream bs;
// write with operator<<
bs << rpp::int32(1) << rpp::int32(2) << 3.14f << std::string("world");
// read with operator>>
bs.rewind();
rpp::int32 a, b;
float f;
std::string s;
bs >> a >> b >> f >> s;
// a=1, b=2, f=3.14, s="world"
Example: Vectors and Containers
rpp::binary_stream bs;
// write a vector — stored as [int32 count][elements...]
std::vector<float> positions = {1.0f, 2.0f, 3.0f, 4.0f};
bs << positions;
// read it back
bs.rewind();
std::vector<float> loaded;
bs >> loaded;
// loaded == {1.0f, 2.0f, 3.0f, 4.0f}
Example: File-Backed Stream with file_writer
#include<rpp/binary_stream.h>// write binary data to a file
{
rpp::file_writer out{"data.bin"};
out << rpp::int32(100) << 2.5f << std::string("payload");
// auto-flushed on destruction
}
// read it back with a file_reader (rpp::file + rpp::binary_stream)
{
rpp::file_reader fr{"data.bin"};
rpp::int32 id = fr.read_int32();
float val = fr.read_float();
std::string s = fr.read_string();
}
Example: Socket Streams
#include<rpp/binary_stream.h>
rpp::socket sock = rpp::socket::connect_to(rpp::ipaddress4{"127.0.0.1", 9000});
// socket_writer buffers writes and flushes to the socket// this is inherently a STREAM based utility
rpp::socket_writer out{sock};
out << rpp::int32(42) << std::string("request data");
out.flush(); // or out << rpp::endl;// socket_reader buffers reads from the socket
rpp::socket_reader in{sock};
if (sock.wait_available(2000))
{
rpp::int32 code = in.read_int32();
std::string response = in.read_string();
}
rpp/binary_serializer.h
Reflection-based binary and string serialization using CRTP.
#include<rpp/timer.h>// capture the current high-accuracy time point
rpp::TimePoint start = rpp::TimePoint::now();
doWork();
rpp::TimePoint finish = rpp::TimePoint::now();
// compute elapsed duration between two time points
rpp::Duration elapsed = start.elapsed(finish);
LogInfo("elapsed: %.3f ms", elapsed.msec());
// or use integer accessors directlyint64_t ms = start.elapsed_ms(finish);
int64_t us = start.elapsed_us(finish);
int64_t ns = start.elapsed_ns(finish);
// TimePoint arithmetic with Duration offsets
rpp::TimePoint deadline = start + rpp::Duration::from_seconds(5);
bool expired = rpp::TimePoint::now() > deadline;
// duration between two TimePoints via subtraction
rpp::Duration delta = finish - start;
// get current local time and extract time-of-day
rpp::TimePoint local = rpp::TimePoint::local();
rpp::Duration tod = local.time_of_day();
LogInfo("time of day: %s", tod.to_string());
Example: Timer — Profiling & Delta Time
#include<rpp/timer.h>// Timer auto-starts on construction
rpp::Timer t;
doWork();
double secs = t.elapsed(); // fractional secondsdouble ms = t.elapsed_millis(); // fractional milliseconds// next() returns elapsed time and restarts the timer
rpp::Timer frame_timer;
for (int i = 0; i < 3; ++i) {
doFrame();
double dt = frame_timer.next(); // delta time since last next() callLogInfo("frame %d: %.4f s", i, dt);
}
// static measure() — wraps a callable and returns its execution timedouble work_time = rpp::Timer::measure([] {
doExpensiveWork();
});
LogInfo("expensive work: %.3f s", work_time);
double work_ms = rpp::Timer::measure_millis([] {
doExpensiveWork();
});
LogInfo("expensive work: %.1f ms", work_ms);
// NoStart mode — create without starting, then start manually
rpp::Timer deferred { rpp::Timer::NoStart };
// ... setup ...
deferred.start();
doWork();
LogInfo("deferred: %.3f s", deferred.elapsed());
Example: StopWatch — Start, Stop & Resume
#include<rpp/timer.h>// create and start a stopwatch
rpp::StopWatch sw = rpp::StopWatch::start_new();
doPhaseOne();
sw.stop(); // freeze the elapsed timedouble phase1 = sw.elapsed(); // stopped time is preservedLogInfo("phase 1: %.3f s", phase1);
sw.resume(); // continue timingdoPhaseTwo();
sw.stop();
double total = sw.elapsed(); // cumulative time for both phasesLogInfo("total: %.3f s", total);
// restart — clears and starts fresh
sw.restart();
doWork();
sw.stop();
LogInfo("restarted: %.3f ms", sw.elapsed_millis());
#include<rpp/math.h>// pi constantsfloat pi_f = rpp::PIf; // 3.14159...double pi_d = rpp::PI; // 3.14159...float s2 = rpp::SQRT2f; // 1.41421...// degrees ↔ radiansfloat rad = rpp::radf(90.0f); // 1.5708 (π/2)float deg = rpp::degf(rad); // 90.0double rad_d = rpp::radf(180.0); // 3.14159...// clamp a value to [min, max]int ci = rpp::clamp(15, 0, 10); // 10float cf = rpp::clamp(-0.5f, 0.0f, 1.0f); // 0.0// linear interpolation: lerp(position, start, end)float mid = rpp::lerp(0.5f, 30.0f, 60.0f); // 45.0float quarter = rpp::lerp(0.25f, 0.0f, 100.0f); // 25.0// inverse lerp: find where a value=45.0 sits in [start, end]float t = rpp::lerpInverse(/*value*/45.0f, /*start*/30.0f, /*end*/60.0f); // 0.5// combine: remap a value from one range to anotherfloat srcT = rpp::lerpInverse(75.0f, 50.0f, 100.0f); // 0.5float remapped = rpp::lerp(srcT, 0.0f, 4.0f); // 0.5 * (4.0-0.0) -> 2.0// floating-point comparisonbool zero = rpp::nearlyZero(0.0001f); // true (default eps=0.001)bool eq = rpp::almostEqual(1.0f, 1.0005f); // truebool eq2 = rpp::almostEqual(1.0f, 1.01f, 0.001f); // false
rpp/minmax.h
SSE-optimized min, max, abs, and sqrt with template fallbacks. These are preferred to std::min()/std::max(), because those require the notoriously bloated algorithm header.
#include<rpp/concurrent_queue.h>
#include<rpp/thread_pool.h>
rpp::concurrent_queue<std::string> queue;
std::atomic_bool done = false;
// producer threadrpp::parallel_task([&] {
queue.push("hello");
queue.push("world");
queue.notify([&] { done = true; }); // safely set flag + notify
});
// consumer — blocks until an item is available or notified
std::string item;
while (queue.wait_pop(item)) {
LogInfo("got: %s", item);
}
// wait_pop returns false when notified with no items (done==true)
Example: Non-Blocking try_pop Polling
#include<rpp/concurrent_queue.h>
rpp::concurrent_queue<int> workQueue;
workQueue.reserve(64); // pre-allocate// producer pushes work itemsfor (int i = 0; i < 10; ++i)
workQueue.push(i);
// consumer polls without blockingint task;
while (workQueue.try_pop(task)) {
LogInfo("processing task %d", task);
}
// batch pop: grab all pending items at once
workQueue.push(100);
workQueue.push(200);
std::vector<int> batch;
if (workQueue.try_pop_all(batch)) {
LogInfo("got %zu tasks at once", batch.size()); // 2
}
Example: Timed wait_pop with Timeout
#include<rpp/concurrent_queue.h>
rpp::concurrent_queue<std::string> events;
// wait up to 100ms for an event
std::string event;
if (events.wait_pop(event, rpp::Duration::from_millis(100))) {
LogInfo("event: %s", event);
} else {
LogInfo("no event within 100ms");
}
// process events until a time limitauto until = rpp::TimePoint::now() + rpp::Duration::from_seconds(5);
while (events.wait_pop_until(event, until)) {
LogInfo("event: %s", event);
}
// loop exits when TimePoint::now() > until
Example: Cancellable wait_pop with Predicate
#include<rpp/concurrent_queue.h>
rpp::concurrent_queue<std::string> messages;
std::atomic_bool cancelled = false;
// consumer with cancellation supportrpp::parallel_task([&] {
std::string msg;
auto timeout = rpp::Duration::from_millis(500);
while (messages.wait_pop(msg, timeout,
[&] { return cancelled.load(); }))
{
LogInfo("msg: %s", msg);
}
// exits when cancelled==true or timeout with no items
});
// cancel from another thread — safely sets flag and wakes waiter
messages.notify([&] { cancelled = true; });
Example: Safe Iteration & Atomic Pop
#include<rpp/concurrent_queue.h>
rpp::concurrent_queue<int> queue;
queue.push(10);
queue.push(20);
queue.push(30);
// safe iteration — holds lock for the duration of the loop
{
for (int val : queue.iterator()) {
LogInfo("val: %d", val);
}
// lock released when iter goes out of scope
}
// atomic copy — snapshot the queue without modifying it
std::vector<int> snapshot = queue.atomic_copy();
// pop_atomic — process and remove in two steps (single-consumer only)int item;
if (queue.pop_atomic_start(item)) {
// item is moved out but still in queue until end()processItem(item); // slow operation while queue is unlocked
queue.pop_atomic_end(); // now remove from queue, any waiting threads are notified that the item is fully processed
}
// or use the callback version
queue.pop_atomic([](int&& item) {
processItem(item);
});
// peek without removingint front;
if (queue.peek(front)) {
LogInfo("front: %d (still in queue)", front);
}
rpp/debugging.h
Cross-platform logging with severity filtering, log handlers, timestamps, and assertions.
#include<rpp/debugging.h>// basic logging at different severity levelsLogInfo("server started on port %d", 8080);
LogWarning("disk usage at %d%%", 92);
LogError("failed to open file: %s", path.c_str());
// conditional assert — logs error if expression is falseAssert(connection != nullptr, "connection was null for user %s", username.c_str());
// configure severity filter — suppress info messagesSetLogSeverityFilter(LogSeverityWarn); // only warnings and errors// enable timestamps: hh:mm:ss.MMMmsLogEnableTimestamps(true, 3, /*time_of_day=*/true);
// strip function names from log outputLogDisableFunctionNames();
// works with std::string, rpp::strview, QString directly — no .c_str() needed
std::string name = "sensor-01";
LogInfo("device: %s", name);
Example: Custom Log Handler
#include<rpp/debugging.h>// C-style callback — receives all log messagesvoidmyLogHandler(LogSeverity severity, constchar* message, int len)
{
if (severity >= LogSeverityError)
writeToFile("errors.log", message, len);
}
SetLogHandler(myLogHandler);
// C++ style — add additional handler with context pointerstructLogger {
FILE* file;
staticvoidhandler(void* ctx, LogSeverity sev, constchar* msg, int len) {
auto* self = static_cast<Logger*>(ctx);
fwrite(msg, 1, len, self->file);
}
};
Logger logger { fopen("app.log", "w") };
rpp::add_log_handler(&logger, Logger::handler);
// later: remove when donerpp::remove_log_handler(&logger, Logger::handler);
Example: Exceptions with ThrowErr & AssertEx
#include<rpp/debugging.h>// throw a formatted runtime_errorThrowErr("invalid config key '%s' on line %d", key, lineNum);
// throw only if assertion failsAssertEx(value > 0, "value must be positive, got %d", value);
// log and re-throw an exceptiontry {
riskyOperation();
} catch (const std::exception& e) {
LogExcept(e, "riskyOperation failed");
}
rpp/stack_trace.h
Cross-platform stack tracing and traced exceptions.
Formats a pre-walked callstack with optional error message prefix
Example: Stack Traces & Traced Exceptions
#include<rpp/stack_trace.h>// get a formatted stack trace from the current location
std::string trace = rpp::stack_trace();
LogInfo("trace:\n%s", trace);
// stack trace with an error message prepended
std::string errTrace = rpp::stack_trace("something went wrong", /*maxDepth=*/16);
// print stack trace directly to stderrrpp::print_trace();
rpp::print_trace("crash context");
// create a runtime_error with embedded stack tracethrowrpp::error_with_trace("fatal: corrupted state");
// traced_exception — exception with automatic stack trace in what()try {
throw rpp::traced_exception{"null pointer dereference"};
} catch (const std::exception& e) {
LogError("%s", e.what()); // includes full stack trace
}
// install SIGSEGV handler — crashes become traced_exceptions, this is quite dangerous, only for throw-away utilities, not full production appsrpp::register_segfault_tracer();
// or: print trace and terminate instead of throwingrpp::register_segfault_tracer(std::nothrow);
// low-level: walk the stack, then inspect individual framesauto callstack = rpp::get_callstack(/*maxDepth=*/32);
for (uint64_t addr : callstack) {
rpp::CallstackEntry entry = rpp::get_address_info(addr);
LogInfo("%s", entry.to_string());
}
#include<rpp/bitutils.h>// create a fixed-size bit array with 128 bits (all cleared)
rpp::bit_array bits { 128 };
// set and test individual bits
bits.set(0); // set bit 0
bits.set(42); // set bit 42
bits.set(7, true); // set bit 7 to true
bits.unset(42); // clear bit 42bool b0 = bits.isSet(0); // truebool b42 = bits.isSet(42); // false// checkAndSet — returns true if the bit was newly setbool wasNew = bits.checkAndSet(10); // true (bit 10 was 0, now 1)bool again = bits.checkAndSet(10); // false (already set)// size queriesuint32_t totalBits = bits.sizeBits(); // 128uint32_t totalBytes = bits.sizeBytes(); // 16// raw byte accessuint8_t byte0 = bits.getByte(0); // first 8 bits as a byte// copy bytes outuint8_t buf[4];
uint32_t copied = bits.copy(0, buf, 4); // copy first 4 bytes// copy negated (inverted) bytesuint32_t negCopied = bits.copyNegated(0, buf, 4);
// reset to a new size (clears all bits)
bits.reset(256);
rpp/close_sync.h
Read-write synchronization helper for safe async object destruction.
#include<rpp/close_sync.h>
#include<rpp/thread_pool.h>classStreamProcessor
{
rpp::close_sync CloseSync; // must be first for explicit lock_for_close
std::vector<char> Buffer;
public:~StreamProcessor()
{
// blocks until all async operations holding shared locks finish
CloseSync.lock_for_close();
}
voidprocessAsync()
{
rpp::parallel_task([this] {
// acquire shared lock — returns early if destructor is runningtry_lock_or_return(CloseSync);
// safe to use members — destructor is blocked
Buffer.resize(64 * 1024);
doWork(Buffer);
// shared lock released at scope exit
});
}
voidexclusiveReset()
{
// exclusive lock — blocks until all shared locks are releasedauto lock = CloseSync.acquire_exclusive_lock();
Buffer.clear();
}
boolisAlive() const { return CloseSync.is_alive(); }
};
rpp/endian.h
Endian byte-swap read/write utilities for big-endian and little-endian data.
Linear bump-allocator memory pools for arena-style allocation (no per-object deallocation). This is ideal for quickly burning through many short-lived objects without the overhead of new/delete or malloc/free. The dynamic pool variant supports multiple blocks that grow as needed. This is ideal for tree/graph data structures that only ever grow and are destroyed all at once.
#include<rpp/memory_pool.h>// create a 4KB fixed-size pool (no per-object deallocation)
rpp::linear_static_pool pool { 4096 };
// allocate raw memoryvoid* raw = pool.allocate(128); // 128 bytes, default 8-byte alignment// allocate typed memory (zero-initialized in pool constructor)float* floats = pool.allocate<float>();
// construct a C++ object in the poolstructParticle { float x, y, z; float life; };
Particle* p = pool.construct<Particle>(1.0f, 2.0f, 3.0f, 1.0f);
// allocate an arrayint* ids = pool.allocate_array<int>(16);
// construct a range of objects (returns element_range)
rpp::element_range<Particle> particles = pool.construct_range<Particle>(10,
0.0f, 0.0f, 0.0f, 1.0f// all 10 particles initialized with these args
);
for (Particle& pt : particles) {
pt.life -= 0.1f;
}
// query remaining spaceint remaining = pool.available();
int total = pool.capacity();
Example: Dynamic Pool — Growing Arena
#include<rpp/memory_pool.h>// starts at 128KB, doubles when full
rpp::linear_dynamic_pool pool { 128 * 1024, /*blockGrowth=*/2.0f };
// allocate objects — pool grows automaticallyfor (int i = 0; i < 10000; ++i) {
auto* node = pool.construct<std::pair<int, float>>(i, float(i) * 0.5f);
}
// total capacity across all blocksint cap = pool.capacity();
rpp/sort.h
Minimal insertion sort for smaller binary sizes compared to std::sort. The insertion sort algorithm is efficient for small arrays (typically < 20 elements) and has minimal overhead, making it ideal for sorting small collections without the code size of a full quicksort implementation. The insertion_sort function is provided with a custom comparator for flexible sorting criteria. The code size difference in embedded environments can be extremely significant
Registers a unit test with given name, factory and autorun flag
Example: Defining a Test Class with TestCase
// test_math.cpp
#include<rpp/tests.h>TestImpl(test_math)
{
TestInit(test_math)
{
// called once when the test class is initialized// setup shared state here
}
TestCleanup()
{
// called once after all test cases finish
}
TestCase(addition)
{
AssertThat(1 + 1, 2);
AssertThat(10 + 20, 30);
AssertNotEqual(1 + 1, 3);
}
TestCase(comparisons)
{
AssertTrue(5 > 3);
AssertFalse(3 > 5);
AssertGreater(10, 5);
AssertLess(3, 7);
AssertGreaterOrEqual(5, 5);
AssertLessOrEqual(5, 5);
}
TestCase(range_checks)
{
// inclusive range [0, 100]AssertInRange(50, 0, 100);
// exclusive range (0, 100)AssertExRange(50, 0, 100);
}
TestCase(string_equality)
{
rpp::strview s = "hello";
AssertThat(s, "hello");
AssertThat(s.length(), 5);
AssertNotEqual(s, "world");
}
TestCase(vectors)
{
std::vector<int> actual = { 1, 2, 3 };
std::vector<int> expected = { 1, 2, 3 };
AssertEqual(actual, expected);
}
};
Example: Exception & Error Assertions
#include<rpp/tests.h>TestImpl(test_exceptions)
{
TestInit(test_exceptions) {}
TestCase(throws_expected)
{
// assert that an expression throws a specific exception typeAssertThrows(throw std::runtime_error{"oops"}, std::runtime_error);
}
TestCase(no_throw)
{
// assert that no exception is thrownAssertNoThrowAny(int x = 1 + 2; (void)x);
}
TestCase(custom_message)
{
int value = 42;
// assert with a custom formatted error messageAssertMsg(value > 0, "value must be positive, got %d", value);
}
// entire test case expects an exception — if it doesn't throw, the test failsTestCaseExpectedEx(must_throw, std::runtime_error)
{
throw std::runtime_error{"this is expected"};
}
};
Example: Test Runner & main()
// main.cpp
#include<rpp/tests.h>intmain(int argc, char** argv)
{
// run tests from command line args:// ./RppTests test_math — run all cases in test_math// ./RppTests test_math.addition — run only the "addition" case// ./RppTests math string — run all tests matching "math" or "string"// ./RppTests — run ALL registered testsreturnrpp::test::run_tests(argc, argv);
}
// or run programmatically with patterns// these can be embedded into your embedded binaries to run tests before main() on actual devicesrpp::test::set_verbosity(rpp::TestVerbosity::AllMessages);
int result = rpp::test::run_tests("test_math");
// run all testsint result = rpp::test::run_tests();
// cleanup all test state (for leak detection)rpp::test::cleanup_all_tests();
Example: Per-TestCase Setup & Cleanup
#include<rpp/tests.h>TestImpl(test_database)
{
Database* db = nullptr;
TestInit(test_database)
{
// called once before all test cases
db = Database::connect("test.db");
}
TestCleanup()
{
// called once after all test casesdelete db;
}
TestCaseSetup()
{
// called before EACH test case
db->beginTransaction();
}
TestCaseCleanup()
{
// called after EACH test case
db->rollback();
}
TestCase(insert)
{
db->exec("INSERT INTO users VALUES ('alice')");
AssertThat(db->count("users"), 1);
}
TestCase(empty_table)
{
// previous insert was rolled backAssertThat(db->count("users"), 0);
}
};
rpp/log_colors.h
ANSI terminal color macros for colorized console output.
Color wrappers:RED(text), GREEN(text), BLUE(text), YELLOW(text), CYAN(text), MAGENTA(text), WHITE(text), DARK_RED(text), BOLD_RED(text), etc.
Color codes:COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_RESET, etc.
// Option 1: Define RPP_DEFINE_JNI_ONLOAD before including the header// This automatically defines JNI_OnLoad for you
#defineRPP_DEFINE_JNI_ONLOAD1
#include<rpp/jni_cpp.h>// Option 2: Manually initialize in your own JNI_OnLoad
#include<rpp/jni_cpp.h>extern"C" jint JNI_OnLoad(JavaVM* vm, void* reserved) {
returnrpp::jni::initVM(vm);
}
Example: Smart References with Ref<T>
// Ref<T> automatically cleans up JNI local/global references via RAIIusingnamespacerpp::jni;// wrap an untracked local ref for automatic cleanup
Ref<jobject> localObj = makeRef(env->NewObject(cls, ctor));
// convert to a global ref that can be stored across JNI calls
Ref<jobject> globalObj = makeGlobalRef(env->NewObject(cls, ctor));
// or convert an existing local ref to global
Ref<jobject> obj = makeRef(env->CallObjectMethod(...));
Ref<jobject> stored = obj.toGlobal(); // safe for static/global storage
Example: JString Conversions
usingnamespacerpp::jni;// create a JNI string from C++
JString greeting = JString::from("Hello from C++!");
int len = greeting.getLength(); // 15// convert JNI string back to C++ std::string
std::string cppStr = greeting.str(); // "Hello from C++!"// wrap a jstring received from JavavoidhandleJavaString(jstring javaStr) {
JString s{javaStr}; // takes ownership, auto-freed
std::string text = s.str();
}
Example: Looking Up Classes, Methods, and Fields
usingnamespacerpp::jni;// find a Java class (always stored as GlobalRef internally)
Class activity{"com/myapp/MainActivity"};
// look up instance and static methods using JNI signatures
Method getName = activity.method("getName", "()Ljava/lang/String;");
Method getInstance = activity.staticMethod("getInstance", "()Lcom/myapp/MainActivity;");
// look up fields
Field appVersion = activity.field("appVersion", "Ljava/lang/String;");
Field instanceCount = activity.staticField("instanceCount", "I");
Example: Calling Java Methods
usingnamespacerpp::jni;
Class player{"com/unity3d/player/UnityPlayer"};
Method sendMessage = player.staticMethod("UnitySendMessage",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
// call static void method with JString arguments
JString obj = JString::from("GameManager");
JString func = JString::from("OnEvent");
JString data = JString::from("{\"score\":100}");
sendMessage.voidF(nullptr, obj, func, data);
// call an instance method that returns a string
Class context{"android/content/Context"};
Method getPackageName = context.method("getPackageName", "()Ljava/lang/String;");
jobject mainActivity = getMainActivity("com/myapp/MainActivity");
JString packageName = getPackageName.stringF(mainActivity);
std::string name = packageName.str(); // "com.myapp"
Example: Accessing Java Fields
usingnamespacerpp::jni;
Class config{"com/myapp/AppConfig"};
// read a static int field
Field maxRetries = config.staticField("MAX_RETRIES", "I");
jint retries = maxRetries.getInt(); // static: no instance needed// read an instance string field
Field userName = config.field("userName", "Ljava/lang/String;");
JString name = userName.getString(configInstance);
std::string user = name.str();
Example: Working with JArray
usingnamespacerpp::jni;// create a Java String[] from C++ strings
std::vector<constchar*> tags = {"debug", "network", "ui"};
JArray tagArray = JArray::from(tags);
// access array elementsint len = tagArray.getLength(); // 3
JString first = tagArray.getStringAt(0); // "debug"// work with primitive arrays (e.g. byte[])
Method getData = myClass.method("getData", "()[B");
JArray bytes = getData.arrayF(JniType::Byte, instance);
{
ElementsRef elems = bytes.getElements();
int size = elems.getLength();
for (int i = 0; i < size; ++i) {
jbyte b = elems.byteAt(i);
}
} // elements released automatically
Development > CircleCI Local
Running CircleCI locally on WSL/Ubuntu.
If running on WSL, you need Docker Desktop with WSL2 integration enabled.