A lightweight single-header C++ Result<T> implementation built around std::variant, inspired by Rust-style result handling.
This library provides:
Ok<T>for successful operationsWarn<T>for non-fatal issuesErrorfor failures- Helper macros like
TRY(...) - Optional value semantics
- Structured logging support
- Zero exceptions design philosophy
Result<Database> initialiseDatabase() {
Database db;
if (!db.connect()) {
return Error("Unable to connect to database server"); // Returns error and logs message
}
return Ok(move(db));
}
Result<> insertUser() {
Database db = TRY(initialiseDatabase()); // Macro to return if Error
if (!db.addUser("Daniel")) {
return Error("Failed to insert user into database");
}
return Ok();
}- Single-header library
- No exceptions required
- Strongly typed result handling
- Supports warnings separately from errors
std::visitcompatible- Automatic message formatting via
fmt - Logging integration via
spdlog - Implicit result conversion between compatible
Result<U>andResult<T> - Convenience macros for early returns
Simply copy ipc_result.h into your project.
- C++17 or newer
spdlogfmt(normally already bundled withspdlog)
Example using CMake:
find_package(spdlog REQUIRED)
target_link_libraries(your_target PRIVATE spdlog::spdlog)This implementation currently uses:
spdlogfor loggingfmtfor formatting
However, the logging calls are intentionally isolated and minimal, making it easy to switch to another logging library.
You can replace:
spdlog::warn(...)
spdlog::error(...)
spdlog::critical(...)with your preferred logger.
The library uses three result states:
| Type | Meaning |
|---|---|
Ok<T> |
Successful operation |
Warn<T> |
Successful operation with warning |
Error |
Failure |
Result<int> getValue() {
return Ok(42);
}Result<int> getValue() {
return Warn(
42,
"Value loaded from fallback cache"
);
}Result<int> getValue() {
return Error(
"Failed to load configuration"
);
}The TRY(...) macro simplifies propagating failures upward.
Instead of manually checking every result:
Result<int> result = otherFunc();
if (!result.hasValue()) {
return result;
}
int value = *result;You can simply write:
int value = TRY(otherFunc());If otherFunc() returns an error or empty value, the current function immediately returns.
Result<int> parsePort() {
return Ok(8080);
}
Result<int> startServer() {
int port = TRY(parsePort());
return Ok(port);
}Result<int> getPort() {
return Ok(8080);
}
Result<int> startServer() {
return TRY(getPort());
}Useful for void functions that still need failure propagation.
Result<int> initialiseDatabase() {
return Ok(1);
}
void bootSystem() {
int db = TRY_VOID(initialiseDatabase());
spdlog::info("Database ready: {}", db);
}TRY_OK(...) only checks whether the result is an error.
It allows empty successful states.
Result<> initialiseLogging() {
return Ok();
}
Result<> startApplication() {
TRY_OK(initialiseLogging());
return Ok();
}Return a custom fallback value if the expression fails.
Result<int> getNumber() {
return Error("Failed");
}
int getNumberOrMinusOne() {
return TRY_OR(getNumber(), -1);
}Result<int> result = getValue();
if (result.isOk()) {
}
if (result.isWarn()) {
}
if (result.isError()) {
}Result<int> result = getValue();
if (result.hasValue()) {
int value = *result;
}if (auto msg = result.message()) {
std::cout << *msg << std::endl;
}The underlying std::variant can be accessed using .raw().
std::visit(overloaded{
[](const Ok<int>& ok) {
std::cout << "Success";
},
[](const Warn<int>& warn) {
std::cout << "Warning";
},
[](const Error& err) {
std::cout << "Error";
}
}, result.raw());This library intentionally avoids exceptions.
Instead:
- Function outcomes are explicit in return types
- Failures propagate naturally
- Warnings are first-class citizens
- Logging occurs automatically
- Optional values prevent accidental invalid access
operator*assumes the result contains a value- Invalid dereference attempts terminate the application
operator->returnsnullptrif no value existsWarn<T>may optionally contain:- a value
- a warning message
- both
MIT License (recommended)