This is a collection of small C++ utilities that I find useful in various projects. Header-only. I use it in almost every C++ project I work on.
C++20 is required to use this library, but the library will progressively be updated to the latest C++ standard.
If MSVC is used, make sure to turn on standard preprocessor via /Zc:preprocessor to enable the __VA_OPT__ macro and conformant __VA_ARGS__; /Zc:__cplusplus is required.
fmt is strongly recommended, but not required. spdlog is also recommended for logging.
Add the following lines to your CMakeLists.txt file; since it's a header-only library, no need to build it(or use FetchContent).:
include(ExternalProject)
set(AUXILIA_INCLUDE_DIR ${CMAKE_BINARY_DIR}/auxilia-src/include)
# Download but don't build
ExternalProject_Add(auxilia_download
GIT_REPOSITORY https://github.com/LiAuTraver/auxilia.git
GIT_TAG main
SOURCE_DIR ${CMAKE_BINARY_DIR}/auxilia-src
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
)
add_library(auxilia INTERFACE)
add_library(auxilia::auxilia ALIAS auxilia)
target_include_directories(auxilia INTERFACE ${AUXILIA_INCLUDE_DIR})
target_compile_features(auxilia INTERFACE cxx_std_23) # or cxx_std_20
add_dependencies(auxilia auxilia_download)name a few. All utilities are in the auxilia namespace.
-
Status: A simple status class for error handling and reporting, mimic fromabsl::Status. -
StatusOr: A class that represents a value or an error status, similar toabsl::StatusOr. Requirement: conceptStorable<Ty>, resembles_STD __SMF_control<...>in Microsoft's STL.
Yes I know there's
Option<T>. However, by the time I finished theStatusOr, I hadn't been taking up the Rust programming languages, not to mentionResult<T, E>.
if (auto maybeResult = func()) {
// do something with the result
std::println("Result: {}", *maybeResult);
} else {
// handle the error
std::println("Error: {}", maybeResult.message());
}
// or conveniently if only printing the result:
std::println("Result: {}", func());
// or more idiomatically,
func()
.and_then([](auto&& result) {
// do something with the result
return new_status_or_with_anything;
})
.or_else([](auto&& error) {
// handle the error
return new_status_or;
});
// additional monadic operations:
func()
.transform([](auto&& result) {
return anything;
})
.transform_error([](auto&& error) {
return new_status;
})
.transform([](auto&& result) {
// no need to return a value, implicitly returns a `Monostate`(inside a `StatusOr`)
})
.transform_error([](auto&& error) {
// no need to return a value, which means the error is not modified and will propagate
});-
Generator: Similar tostd::generatorin C++23, used in some platforms where C++23'sstd::generatoris not fully supported. -
MemoryPool: A simple memory pool allocator which allocates fix-sized memory on the stack. -
Monostate,Variant:std::variantwrapper with more functionality. Say goodbye to awfully longstd::holds_alternative. The types used inVariantmust satisfyVariantable<...>concept: the first type must beMonostateor class derived fromMonostate, and the rest must bedefault constructibleandStorable.tested that the
templatekeyword is not needed(e.g.,v.template get<Ty>) with all current major three compilers.
Variant<Monostate, int, std::string> v = 1;
if (auto ptr = v.get_if<int>()) {
std::println("int: {}", *ptr);
}
v = "Hello world!";
if (v.is_type<std::string>()) {
std::println("string: {}", v.get<std::string>());
}
v = Monostate{}; // reset to monostate, or v.reset()
v.visit(match(
[](const int& i) { std::println("int: {}", i); },
[](const std::string& s) { std::println("string: {}", s); },
[](const auto&) { std::println("don't care"); } // catch-all
));
// or when multiple variants are needed:
auxilia::visit(pattern, v1, v2, v3, ...);
// printing:
std::println("{}", v); // prints "class auxilia::Variant<struct auxilia::Monostate,int,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > "
std::println("{}", v.to_string()); // prints "Monostate" or "1" or "Hello world!" depending on the type of `v`-
Trie: I used it in cp as a part of left factoring implementation. -
Noise: A utility class to track object lifetimes and copy/move operations for debugging purposes. For example, we got the best performance of monadic function ofStatusOronly follows:
StatusOr<Noise<>>() // rvalue, otherwise copy ctor will be called
.and_then([](auto &&val) {
Println("Got Noise from StatusOr");
return StatusOr<Noise<>>(std::move(val)); // move ctor, otherwise copied
})
.and_then([](auto &&val) {
Println("Chained Noise from StatusOr");
return StatusOr<Noise<>>(std::move(val)); // ditto
})
.transform([](auto &&val) {
std::cout << val;
});Don't use
Noisein production code where RTTI is enabled, since it's message is derived from NTTP; the typeinfo would be hilariously long.
-
Property: C#-like property system for C++ full of syntax sugar. -
Printable,Viewable: template-less static interface forfmt::printandfmt::format_to, or possiblystd::printandstd::format_toin C++23.
struct MyStruct : Printable {
// no need to override
auto to_string(const ::auxilia::FormatPolicy policy = ::auxilia::FormatPolicy::kDefault) const -> string_type {
return "a string";
}
};
MyStruct s;
std::println("MyStruct: {}", s); // prints "MyStruct: a string"-
id: Thread-safe simple function to assign unique IDs to objects. -
rand_u8(oru16, u32, u64, maybeu128): Numpy-like random number generator without writing boilerplate code likestd::random_deviceandstd::mt19937every time. -
views::trim: A view that trims whitespace-like characters from the beginning and end of a string. Pipe operator-chainable. -
read_as_bytes: read a file as binary, with more efficiency and better error handling. Endianness is supported. -
chars(orwchars, u8chars, u16chars, u32chars): A simple wrapper for compile-time string literals. -
bitset: A simple bitset class with the same interface asstd::bitset, but with more functionalities with personalization. Note: Performance overhead exists due to my inability in vectorization optimization. This exists only for my own usage. -
is_specialization_v,is_any_of_v: Type traits to check if a type is a specialization of a template, or if a type is any of the given types.
Most of the functionalities must be enabled by defining the variable
AC_UTILS_DEBUG_ENABLEDto1or set the environment variableAC_CPP_DEBUGtoONor inCMakeLists.txt.
defer: Similar todeferin Go, allows you to defer some execution until the end of the scope. This may make the code less-readable, but it is useful in some cases.
defer { /* do something */ }; // don't forget semicolon-
contract_assert,preconditionandpostcondition: initialy an idea proposed for C++26(now accepted, thus sometime maybe I'll have their name changed), these macros are used to assert preconditions and postconditions in a function. Precondition and Postcondition won't be checked in release mode, but contract_assert will be checked in both debug and release mode. This is useful for debugging and testing purposes. Semicolon is not needed but better add it to.clang-formatasStatementAttributeLikeMacrosin order to avoid iℕℂöṙṙĕℂţ FöṙMäṮţĭng.Furthermore, those assertion triggers a debug break when debugger is attached, so you can easily debug the code when it fails(Rather than an awkward Microsoft C++ Runtime Library window popping up and terminates the program), otherwise prints stacktrace and aborts the program. I found it more useful both than
assertandboost::contract::checkboilerplate code.
note: the functionalities of
pre,postandcontract_assertis slightly different from the original proposal. I just borrowed the name.
void func(int x) {
contract_assert(x > 0, "x must be greater than 0"); // will be checked in debug mode
precondition(x > 0, "x must be greater than 0") // will be checked at once, so ensure put it at the beginning of the function
postcondition(x < 10) // will be checked in when the function returns
// do something
}dbg: Withspdlog, this macro is used to print debug information with zero cost at release mode.spdlog::debugcomes with an external dependency, also persists in release mode so it comes with a cost. ditto, it's a debug utility.
AC_SPDLOG_INITIALIZATION(myapp, info); // log with `info` level and above will be printed
dbg(debug, "Hi!"); // equivalent to `spdlog::debug("Hi!")`
dbg(error, "Error: {}", msg); // ditto-
TODO(): Marks a line of code as a TODO. It will be highlighted in the IDE and can be used to track TODOs in the codebase. Throws exception in release mode; if exceptions are disabled, raisesSIGABRTsignal. -
DebugUnreachable: Marks a line of code as unreachable. It will be checked in debug mode and will trigger debugbreak just like above. -
dbg_block: A block of code that will be executed only in debug mode.
dbg_block {
// this code will be executed only in debug mode
};AC_BITMASK_OPS(_bitmask_): from Microsoft's STL implementation, bring some bit operations for scoped enums.
in auxilia/net.hpp. I aimed to mimic Boost.Asio, yet I failed a bit; no sender/receiever, executor or high-performat asio::strand but mutex and lifetime traps... :(
it contains very basic socket operations, see chatroom demo for exmaples.
NOTE: the TCP socket has deadlock bugs and performance panelty on Linux, since I mainly developed it on Windows.
TODO: replace epoll with io_uring API.
The network part is not included in
auxilia/auxilia.hppdue to the inclusion of system headers like the infamousWindows.h. deducing this is used extensively here, hence the mininum required version is C++23.fmtandspdlogis required to build this demo.
Some miniprojects of myself that directly use auxilia are located in the projects folder. Add -DAUXILIA_BUILD_PROJECTS=ON to CMake to build them.
A considerable part of the idea comes from stackoverflow, existing libraries, or my own projects. Their inspiration are documented in the code. If you find any bugs or have any suggestions, feel free to open an issue or a pull request.
Apache License 2.0