Welcome to clu Reference!
Chlorie’s Utilities (clu) is a library consisting of many seemingly unrelated independent components. It started out being a header-only library, but then I decided to separate some of the implementation details into source files.
The library covers a range of different use cases, from tiny utilities that the standard library does not provide (or hides as implementation details, why), to more complex data structures and algorithms that are handy to use in everyday programming.
Warning
This library is mainly created by Chlorie for personal use, so a large portion of this library is still untested and lacking documentation. Please use at your own risk.
Have fun!
Components
Most of the components are under the main include directory clu
. Some of the core headers (like type_traits.h
) are used by other ones, while most of the other headers aren’t depended upon. Things under clu/experimental
are highly unstable, some of them may not even compile.
A list of all the headers and their contents is presented as follows:
- Header any_unique.h
Provides a type-erasure class to store objects of unknown types.
assertion.h
Defines a macro
CLU_ASSERT(expr, msg)
, which functions roughly the same asassert
from<cassert>
, but takes an additional message parameter.buffer.h
A buffer class template, similar to
std::span
but more suitable for raw buffer manipulation.- Header c_str_view.h
View types for null-terminated strings.
chrono_utils.h
Utilities related to
std::chrono
. Providing a centralized namespace for all the constants instd::chrono
likestd::chrono::January
. Also some helper functions.concepts.h
Useful concepts for meta-programming.
expected.h
A sum type storing a value or an error
Ok | Err
, likeResult
in Rust. A rough implementation of P0323R10:std::expected
.- Header file.h
Contains a function
read_all_bytes
that reads all contents of a binary file into astd::vector
.fixed_string.h
A fixed string class that can be used as class non-type template parameters.
flags.h
A wrapper for flag enumeration types.
forest.h
An STL-styled hierarchical container type.
function_ref.h
Non-owning type-erasure types for invocables.
function_traits.h
Type traits for function types. Provides meta-functions for extracting parameter types or result type from function signatures.
- Header hash.h
Hash-related utilities. Provides
constexpr
implementations of FNV-1a and SHA1 hash functions, and ahash_combine
function.indices.h
A random-access range for multi-dimensional indexing, so that you could use
for (const auto [i, j, k] : indices(3, 4, 5))
instead of nestedfor
loops.integer_literals.h
UDLs for fixed-length integral literals, like
255_u16
.iterator.h
Adapter class template for iterators. Provides default implementations for the iterator concepts.
manual_lifetime.h
Helper class for manually controlling lifetime of an object.
meta_algorithm.h
Algorithms for type lists.
meta_list.h
Type lists and value lists for meta-programming.
new.h
Helpers for aligned heap allocation.
oneway_task.h
A fire-and-forget coroutine type which starts eagerly.
optional_ref.h
std::optional
like interface for optional references.overload.h
Overload set type. Useful for
std::visit
.scope.h
Scope guards for running arbitrary code at destruction. Useful for ad-hoc RAII constructs.
static_for.h
Loop over compile time indices. Can also be used as a way to force loop unrolling.
static_vector.h
std::vector
like container allocated on the stack, with a fixed capacity.string_utils.h
String-related utilities.
type_traits.h
Useful type traits for meta-programming.
piper.h
“Pipeable” wrappers for invocables, like those ranges adapters.
polymorphic_value.h
A smart pointer type for copyable polymorphic types. A rough implementation of P0201R3: A polymorphic value-type for C++.
polymorphic_visit.h
std::visit
, but for polymorphic types.unique_coroutine_handle.h
An RAII type for
std::coroutine_handle<T>
which destroys the handle on destruction.vector_utils.h
A
make_vector
function to remedy the fact that we cannot usestd::initializer_list
to initialize containers with move-only elements.
Header any_unique.h
Provides a type-erasure class to store objects of unknown types.
Examples
1#include <clu/any_unique.h>
2
3int main()
4{
5 clu::any_unique a(1); // a holds an int
6 a.emplace(std::in_place_type<double>, 3.14); // now a holds a double, and the previous int is destroyed
7 a.reset(); // a holds nothing
8
9 struct immovable
10 {
11 immovable() = default;
12 immovable(immovable const&) = delete;
13 immovable(immovable&&) = delete;
14 immovable& operator=(immovable const&) = delete;
15 immovable& operator=(immovable&&) = delete;
16 };
17
18 clu::any_unique b(std::in_place_type<immovable>); // immovable objects can also be stored, using in_place_type
19}
-
class any_unique
A type erasure class that could store literally anything.
This class type erases objects and always store them on the heap. It is almost of no use but to provide a way to manage lifetime of objects with compile-time unknown types.
Public Functions
-
constexpr any_unique(const any_unique&) = delete
any_unique
is not copyable.
-
constexpr any_unique(const any_unique&) = delete
Header buffer.h
-
namespace clu
Typedefs
-
using mutable_buffer = basic_buffer<std::byte>
Mutable buffer using
std::byte
as the element type.
-
using const_buffer = basic_buffer<const std::byte>
Constant buffer using
std::byte
as the element type.
Functions
-
template<alias_safe T>
constexpr void memmove(T *dst, const T *src, const size_t size) noexcept Copies data between addresses, safe even when the destination overlaps the source.
- Parameters
dst – Destination address.
src – Source address.
size – The size (counting T’s, not bytes) to copy.
-
template<buffer_safe T>
mutable_buffer trivial_buffer(T &value) noexcept
-
template<buffer_safe T>
const_buffer trivial_buffer(const T &value) noexcept
Variables
- template<typename T> concept alias_safe = same_as_any_of<T, unsigned char, char, std::byte>
Specifies that a type is safe to alias.
An alias-safe type could be used as buffer elements.
- template<typename T> concept buffer_safe =trivially_copyable<T> ||(std::is_array_v<T> && trivially_copyable<std::remove_all_extents_t<T>>)
Specifies that a type is safe to be seen through by a buffer.
Buffer-safe types can be read from/written into buffers.
- template<typename T> concept trivial_range =std::ranges::contiguous_range<T> &&std::ranges::sized_range<T> &&trivially_copyable<std::ranges::range_value_t<T>>
- template<typename T> concept mutable_trivial_range = trivial_range<T> && (!std::is_const_v<std::ranges::range_value_t<T>>)
-
template<typename T>
class basic_buffer - #include <buffer.h>
Non-owning buffer view, suitable for raw byte manipulations.
- Template Parameters
T – The buffer element type, must be alias-safe.
Public Types
-
using mutable_type = basic_buffer<std::remove_const_t<T>>
-
using const_type = basic_buffer<std::add_const_t<T>>
Public Functions
-
constexpr basic_buffer() noexcept = default
Creates an empty buffer.
-
constexpr basic_buffer(const basic_buffer&) noexcept = default
-
constexpr basic_buffer(basic_buffer&&) noexcept = default
-
constexpr basic_buffer &operator=(const basic_buffer&) noexcept = default
-
constexpr basic_buffer &operator=(basic_buffer&&) noexcept = default
-
inline constexpr basic_buffer(T *ptr, const size_t size) noexcept
Constructs a buffer from a start pointer and a size.
- Parameters
ptr – Start of the buffer region.
size – Size of the buffer.
- template<typename R> inline requires (std::is_const_v< T > &&trivial_range< R >)||(!std
- template<typename = int> inline requires std::is_const_v< T > constexpr explicit (false) basic_buffer(const mutable_type &buffer) noexcept
-
inline constexpr size_t size() const noexcept
Gets the size of the buffer.
-
inline constexpr bool empty() const noexcept
Checks if the buffer is empty.
-
inline constexpr void remove_prefix(const size_t size) noexcept
-
inline constexpr void remove_suffix(const size_t size) noexcept
-
inline constexpr basic_buffer first(const size_t size) const noexcept
-
inline constexpr basic_buffer last(const size_t size) const noexcept
-
inline constexpr basic_buffer &operator+=(const size_t size) noexcept
-
inline constexpr std::span<T> as_span() const noexcept
Converts the buffer into a
std::span
of the element type.
-
inline constexpr size_t copy_to(const mutable_type dest) const noexcept
-
inline constexpr size_t copy_to_consume(const mutable_type dest) noexcept
Private Static Functions
Friends
- inline friend constexpr friend basic_buffer operator+ (basic_buffer buffer, const size_t size) noexcept
-
using mutable_buffer = basic_buffer<std::byte>
Header c_str_view.h
-
template<typename Char>
class basic_c_str_view A string view type for null-terminated strings.
- Template Parameters
Char – The character type
Public Functions
-
inline constexpr const_reference front() const noexcept
Get the first character.
-
inline constexpr size_t size() const noexcept
Get the length of the string, excluding the null terminator
Remark
This function is not \(O(1)\). It calls
strlen
.
-
inline constexpr bool empty() const noexcept
Check if the view is empty.
-
inline constexpr void remove_prefix(const size_t n) noexcept
Remove prefix with a certain length from the view.
-
inline constexpr void swap(basic_c_str_view &other) noexcept
Swap two views.
Friends
- inline friend constexpr friend void swap (basic_c_str_view &lhs, basic_c_str_view &rhs) noexcept
Swap two views.
-
struct sentinel
Sentinel type.
Header file.h
This header provides helper functions to read/write data from/to files easily.
Examples
The following code snippet reads a file and computes SHA-1 hash of the contents:
1#include <clu/file.h>
2#include <clu/assertion.h>
3
4int main()
5{
6 std::vector<int> data{ 1, 2, 3, 4 };
7 clu::write_all_bytes("data.bin", data);
8 const auto read = clu::read_all_bytes<int>("data.bin"); // read as vector of ints
9 const auto bytes = clu::read_all_bytes("data.bin"); // default behavior is to read as vector of bytes
10 CLU_ASSERT(read == data);
11
12 clu::write_all_text("data.txt", "Hello, world!");
13 const std::string text = clu::read_all_text("data.txt");
14 CLU_ASSERT(text == "Hello, world!");
15}
-
template<trivially_copyable T = std::byte, allocator_for<T> Alloc = std::allocator<T>>
std::vector<T, Alloc> clu::read_all_bytes(const std::filesystem::path &path, const Alloc &alloc = Alloc{}) Read contents of a binary file into a
std::vector
.- Template Parameters
T – Value type of the result vector. It must be trivially copyable.
Alloc – Allocator type used by the vector.
- Parameters
path – The path to the file to read from.
alloc – The allocator for allocating the vector.
-
std::string clu::read_all_text(const std::filesystem::path &path)
Reads contents of a text file into a
std::string
.- Parameters
path – The path to the file to read from.
-
void clu::write_all_bytes(const std::filesystem::path &path, const_buffer bytes)
Writes bytes in a given buffer into a file. The file will be overwritten.
- Parameters
path – The path to the file to write into.
bytes – The byte buffer.
Header hash.h
This header provides hash-related helper functions and common hash algorithms. The algorithms are constexpr
enabled if possible.
Note
Unless specified otherwise, if the digest type of a hasher is a std::array
of unsigned integers (words), the most significant word always comes last (little endian). The individual words are in native byte order, which may not be little endian.
Examples
The following code snippet reads a file and computes SHA-1 hash of the contents:
1#include <iostream>
2#include <format>
3#include <clu/file.h>
4#include <clu/hash.h>
5
6int main()
7{
8 const auto bytes = clu::read_all_bytes("my_file.txt");
9 const auto hash = clu::sha1(bytes);
10 std::cout << std::format("SHA-1: {:08x}{:08x}{:08x}{:08x}{:08x}\n",
11 hash[0], hash[1], hash[2], hash[3], hash[4]);
12}
We cannot use strings in switch
-es, but utilizing the constexpr
hash algorithms we are able to imitate that feature. Hash conflicts of cases are also detected at compile time which is neat. Just don’t use this on arbitrary user inputs, where weird (read “malicious”) hash clashes may occur.
1#include <clu/hash.h>
2
3int main()
4{
5 using namespace clu::literals; // for enabling the UDLs
6 switch (clu::fnv1a("second")) // regular function call
7 {
8 case "first"_fnv1a: return 1; // or use UDLs
9 case "second"_fnv1a: return 2;
10 case "third"_fnv1a: return 3;
11 }
12}
Guides
Since this project uses latest features of C++ aggressively, some of the concepts used in this library might seem unfamiliar to you. This part of the documentation is intended to help you to understand these concepts.
Coroutines
The concept of coroutine is nothing new, the first occurence of which dates back to the 1950s. It is an extension on the “subroutine” (or “function”) concept, supporting “suspension” (or “pause”) and “resumption” of execution between routines. A coroutine can be viewed as a kind of syntactic sugar of a state machine. Below is a simple illustration of coroutine execution in pseudo-C++ code.
1Coro coroutine(int& value)
2{
3 $suspend(); // suspension point #1
4 value = 42;
5 $suspend(); // suspension point #2
6 return;
7}
8
9int main()
10{
11 int value = 0;
12
13 // coro runs to #1 and suspends,
14 // control flow returns to main
15 Coro coro(value);
16
17 // Resume the execution of coro,
18 // from where it was suspended (#1).
19 // It will set value to 42 and suspend again.
20 coro.$resume();
21
22 assert(value == 42);
23
24 // This time coro resumes from #2,
25 // and runs until the end (return statement).
26 coro.$resume();
27
28 return 0;
29}
In this example the control flow ping-pongs between the coroutine and the main function, which is a typical case for generator coroutines.
Generators
A generator is a lazy range of values, in which each value is generated (yielded) on demand. Many other languages have some concrete implementation of generators, for example C# functions returning IEnumerable<T>
with yield return
statements in the function body, or Python generators with yield
statements.
C++20 only provides us with the most bare-bone API of coroutines, with all the gnarly details exposed for coroutine frame manipulations and but support from the library side for user-facing APIs. There is currently a new std::generator
class template proposed for the C++23 standard, and if you don’t want to wait for another who-knows-how-many years, you can use the generator coroutine implementation in this library.
This library provides clu::generator<T>
which supports yielding values from the coroutine body using the co_yield
keyword. The following example shows how to use it.
1#include <iostream>
2#include <clu/generator.h>
3
4// A never-ending sequence of Fibonacci numbers
5clu::generator<int> fibonacci()
6{
7 int a = 1, b = 1;
8 while (true)
9 {
10 co_yield a; // Yielding a value
11 a = b;
12 b = a + b;
13 }
14}
15
16int main()
17{
18 // Generators are input ranges,
19 // we can use them in range-based for loops
20 for (const int i : fibonacci())
21 {
22 std::cout << i << ' ';
23 if (i > 100)
24 return 0;
25 }
26}
For more details, see the documentation of clu::generator
.
Warning
There is no documentation yet…
In the generator case, the coroutine execution is managed by the caller, since each time the caller needs another value from the generator, it resumes the coroutine from the last suspension point.
Asynchronous Tasks
A coroutine can also put its handle into some other execution context, for example, into a thread pool, such that the coroutine could be scheduled to run in parallel with other coroutines. This is the typical case for asynchronous tasks. This is typically implemented in other languages with the async-await construct.
If there were an async network library supporting coroutines, the code below would be an example of how to use it.
1#include <awesome_coro_lib.h>
2
3// A coroutine that performs an asynchronous task.
4// C++ coroutines don't need special `async` keyword,
5// any function that contains `co_await` `co_yield` or
6// `co_return` will be considered a coroutine.
7task<std::string> get_response(const std::string& url)
8{
9 http_client client;
10 // Use the co_await keyword to suspend the coroutine,
11 // and after the request is completed, the coroutine
12 // will the resumed with the response.
13 std::string response = co_await client.get_async(url);
14 co_return response;
15}
In this example, client.get_async(url)
would return an “awaitable” object. Applying co_await
on the awaitable object will suspend the coroutine and wait for someone to resume. For asynchronous operations like this, the suspended coroutine’s resumption is typically registered as a “callback”, called after the asynchronous operation completes.
The clu library provides clu::task<T>
to support this kind of asynchronous usage.
Note that the semantics of task
here differs from that of some other languages that supports the async-await construct, such as C#. The C# Task<T>
type starts eagerly, in the meaning that once a function returning Task<T>
is called, the task is already running and runs parallel with the caller. This kind of detached execution means that we can no longer pass references to local variables as parameters to eager tasks, since the caller might never await on the callee task and thus might return sooner than the callee.
1eager_task<void> callee(const std::string& str)
2{
3 co_await thread_pool.schedule_after(30s);
4 // Now str is dangling...
5 std::cout << str << '\n'; // Boom!
6}
7
8eager_task<void> caller()
9{
10 std::string message = "Hello world!";
11 callee(message);
12
13 // Now we exit the coroutine, destroying
14 // the message string, leaving a dangling
15 // reference to the callee, since callee
16 // is still waiting for the 30s delay.
17 co_return;
18}
We don’t want to pay the cost of copying the parameters to every coroutine we call, nor do we want to reference count everything. Thus we should practice the idiom of “structured-concurrency”, make sure that the callee is finished before the caller is done with it. clu::task
is a lazy task type, which means that the callee is not started until the caller awaits on it.
1clu::task<void> callee(const std::string& str)
2{
3 co_await thread_pool.schedule_after(30s);
4 std::cout << str << '\n';
5}
6
7clu::task<void> caller()
8{
9 std::string message = "Hello world!";
10
11 // Since clu::task is lazy, the following does nothing.
12 // callee(message);
13
14 co_await callee(message);
15 // Now we can safely destroy the message string.
16}