STX is a collection of libraries and utilities designed to make working with C++ easier and less error-prone.
- Experimental async library, runtime, and scheduler
- C-API-compatible functor object
Fn<Return(Args...)> - Memory allocators
- Resource reference-counting
Rc<Handle> Span<T>with helper methods that eliminates use of iterator-pairsVec<T>with trivial relocation (faster thanstd::vector<T>)- Efficient
Result<T, Err>(error-handling) and with monadic methods - Efficient
Option<T>(optional-value handling) with monadic methods - UTF-8 string iterator method
- Fail-fast (Abandonment/ Fatal failure) via
panics - Runtime panic hooks
- Backtrace library
- Source Location
- Panic with backtraces
- Portable, suitable, and easily-adoptable for embedded systems, real-time systems, safety-critical systems, and operating systems
- Easy debugging
- Easy to use and hard to misuse API
- Exception-free, RTTI-free, and memory allocation free (
no-std) - Space and time deterministic error-handling
- Deterministic value lifetimes
- Eliminates repetitive code and abstractable error-handling logic code via monadic extensions
- Fast success and error return paths
- Modern and clean API
- Well-documented
- Extensively tested
#include <iostream>
#include "stx/option.h"
using stx::Option, stx::Some, stx::None;
auto safe_divide(double numerator, double denominator) -> Option<double> {
if (denominator == 0.0) return None;
return Some(numerator / denominator);
}
int main() {
safe_divide(5.0, 2.0).match(
[](auto value) { std::cout << "Result: " << value << std::endl; },
[]() { std::cout << "Cannot divide by zero" << std::endl; });
}
#include <array>
#include <cinttypes>
#include <iostream>
#include "stx/result.h"
using std::array, std::string_view;
using namespace std::literals;
using stx::Result, stx::Ok, stx::Err;
enum class Version { V1 = 1, V2 = 2 };
auto parse_version(array<uint8_t, 6> const& header) -> Result<Version, string_view> {
switch (header.at(0)) {
case 1:
return Ok(Version::V1);
case 2:
return Ok(Version::V2);
default:
return Err("Unknown Version"sv);
}
}
int main() {
parse_version({2, 3, 4, 5, 6, 7}).match([](auto version){
std::cout << "Version: " << static_cast<int>(version) << std::endl;
}, [](auto error){
std::cout << error << std::endl;
});
}
TRY_OK assigns the successful value to its first parameter version if parse_version returned an Ok , else propagates the error value.
// As in the example above
auto parse_version(array<uint8_t, 6> const& header) -> Result<Version, string_view>;
auto parse_data(array<uint8_t, 6> const& header) -> Result<uint8_t, string_view> {
TRY_OK(version, parse_version(header));
return Ok(version + header[1] + header[2]);
}
int main() {
auto parsed = parse_data({2, 3, 4, 5, 6, 7}).unwrap();
std::cout << parsed << std::endl;
}
You can also add const/volatile attributes to TRY_OK 's assigned value, i.e:
auto parse_data(array<uint8_t, 6> const& header) -> Result<uint8_t, string_view> {
TRY_OK(const version, parse_version(header));
return Ok(version + header[1] + header[2]);
}
-
Result and Option will only work in
constexprcontext (compile-time error-handling) in C++ 20, to check if you can use it asconstexprcheck if the macrosSTX_RESULT_IS_CONSTEXPRandSTX_OPTION_IS_CONSTEXPRare set to1, for an example seeconstexpr_test. -
To ensure you never forget to use the returned errors/results, raise the warning levels for your project (
-Wall-Wextra-Wpedanticon GNUC-based compilers, and/W4on MSVC) -
Some methods like
map,unwrap,or_else, and most ofResultandOption's monadic methods consume the stored value and thus theResultorOptionhas to be destroyed as its lifetime has ended. For example:Say we define a function named
safe_divideas in the example above, with the following prototype:
auto safe_divide(float n, float d) -> Option<float>;And we call:
float result = safe_divide(n, d).unwrap(); // compiles, because 'safe_divide' returns a temporaryOption option = safe_divide(n, d);
float result = option.unwrap(); // will not compile, because 'unwrap' consumes the value and is only usable with temporaries (as above) or r-value references (as below)Alternatively, suppose the Option or Result is no longer needed, we can obtain an r-value reference:
Option option = safe_divide(n, d);
float result = std::move(option).unwrap(); // will compile, the value is moved out of 'option' , 'option' should not be used any more
NOTE: Just as any moved-from object, Option and Result are not to be used after a std::move ! (as the objects will be left in an unspecified state).
ResultandOptiondo not make any implicit copies of the contained object as they are designed as purely forwarding types, this is especially due to their primary purpose as return channels in which we do not want duplication nor implicit copies of the returned values.
To make explicit copies:
Option option = safe_divide(n, d);
float result = option.clone().unwrap(); // note that 'clone()' explicitly makes a copy of the 'Option'
We can also obtain an l-value reference to copy the value:
Option option = safe_divide(n, d);
float result = option.value(); // note that 'value()' returns an l-value reference and 'result' is copied from 'option''s value in the process
float result = safe_divide(n, d).value(); // this won't compile as 'value' always returns an l-value reference, use 'unwrap()' instead
- All methods of
ResultandOptionpass r-value/l-value references to their invocable parameters.
- CMake
- Make or Ninja Build
- C++ 17 or C++ 20 Compiler
- Git
- Doxygen and Graphviz (for documentation)
NO