custom types
¶
standard types
¶
Defines standard library imports and useful aliases. Also includes feature testing for the latest c++20
features, which in case they are not available it replaces them with compatible libraries.
standard library features¶
Features from the standard library will be added with a clear intent, trying to keep them as organized as possible. All imports used will be listed in this file.
math
<cstdint>
for standard integer types<random>
for generating pseudo-random numbers<numbers>
for mathematical constants, including pistd::clamp
strings
std::string
(aliased asstr
)std::string_view
(aliased as str_view)
containers
common:
std::array
as the main container, use wherever possiblestd::vector
only for containers that must be variable, preallocation is recommendedstd::unordered_map
for key-value pairsstd::map
only when ordered access is required<ranges>
[c++20
] for algorithms and iteration
specific:
std::deque
for lists where a lot of elements are added to or removed at the backstd::set
for unique identifier lists, has useful mathematical propertiesstd::stack
for LIFO containers, for example, system initialization/destructionstd::queue
andstd::priority_queue
for other ordered containers
metaprograming
- templates
- lambdas (and templated lambdas [
c++20
]) - concepts [
c++20
] std::optional
<tuple>
compiler
- feature testing [
c++20
] std::source_location
[c++20
] for unit testing (alternative implementation provided)
time
std::chrono
for system independent timers
concurrency
- coroutines [
c++20
] std::jthread
[c++20
] for multithreadingstd::atomic
,std::mutex
andstd::condition_variable
for thread syncronization
aliases¶
Some standard types are aliased to improve on readability and code clutter. Also, this is made to avoid using the std
namespace, as that can bring confusion and errors. I tried to use common and easy to understand aliases, and a table is available with all conversions:
type | alias |
---|---|
std::uint8_t |
ui8 |
std::uint16_t |
ui16 |
std::uint32_t |
ui32 |
std::uint64_t |
ui64 |
std::string |
str |
std::string_view |
str_view |
The namespace aliases are mainly to allow compatibility between the standard library and helper libraries while compilers get support of c++20
. For example, the range-v3
library defines everything in the namespace ranges
, while the standard library uses std::ranges
. Similarly, in clang
coroutine features are under std::experimental
, while on gcc
they are under std
. Once the libraries are homogeneous this aliases might be removed.
namespace | alias |
---|---|
std::ranges |
ranges |
std::ranges::views |
rv |
std::experimental |
std_ |
type name
¶
Constexpr compiler-independent type name implementation.
constexpr str_view fresa::type_name_n<TYPE>();
constexpr str_view fresa::type_name<TYPE>();
There are two versions of the function. type_name_n
returns the full type including all namespaces, while type_name
removes fresa
namespaces and only returns the name of the type.
type_name_n<fresa::detail::array>() == "fresa::detail::array";
type_name<fresa::detail::array>() == "array";
//: while
type_name_n<std::array>() == "std::array";
type_name<std::array>() == "std::array";
You can also get an unique hash for a type using type_hash
:
type_hash<fresa::detail::array>() == ...;
constexpr for
¶
Approach of a variety of constexpr for loops. Uses template metaprograming to create the iteration on compile time, supporting constexpr results.
integral for
Similar to a regular for loop, has a range of integral values [a, b) and calls the function inside each time with a different value of i
. In the case of the constexpr for, i
is an integral constant which can be used as a constexpr, for example for template parameters.
//: regular for
for (int i = 0; i < 5; i++) { ... }
//: constexpr for
for_<0, 5>([&](auto i){
...
});
//: also supports a custom increment
for_<0, 30, 3>([](auto i){ ... });
parameter pack for
Recursively calls the function with each of the elements provided. The values can be heterogeneous.
for_([&](auto a){
...
}, 2, 4, 8, 16);
tuple like for
Very similar to the previous one, but perhaps more useful. Iterates over the values of a tuple like object, much like a range based for loop.
//: range based for loop
for (auto a : some_array) { ... }
//: constexpr tuple-like for loop
for_([&](auto const& a){
...
}, std::make_tuple(true, 1, "hello"));
string utils
¶
string literal
Used for passing a string literal as a template parameter.
template<str_literal name>
void function() {
name.value;
}
function<"hey">();
lowercase
Returns a lowercase string view. Works for constexpr strings. There is also a literal operator for in place conversion.
fresa::lower("FrEsA") == "fresa"
"MeRmElAdA"_lower == "mermelada"
split
Parses a string view and returns a range of elements separated by the delimiter (by default a space). The returned object is a range, but can be easily convertible to a vector using ranges::to_vector
.
auto s_range = split("a,b,c,d", ',');
auto s_vector = s_range | ranges::to_vector;
//: removes extra delimiters
auto s = split("a b c d") | ranges::to_vector == {"a", "b", "c", "d"};
atomic queue
¶
spin lock
Simple implementation of a spin lock using std::atomic_flag
for thread syncronization. Can be used inside std::lock_guard
.
fresa::SpinLock lock;
//...
{
std::lock_guard<SpinLock> guard(lock);
//: thread safe code
}
atomic queue
Adaptation of std::queue
to be thread safe using SpinLock
. Can't be copied, only moved (for example, to emplace in a vector or atomic queues). One of its main usages is for the job system's worker queues.
fresa::AtomicQueue<T> queue;
//: add to the end of the queue
queue.push(T{});
//: get an item from the front of the queue and remove it
// if the queue is empty, return an empty optional
std::optional<T> t = queue.pop();
//: elements left in the queue
std::size_t n = queue.size();
//: clear the queue
queue.clear();
bidirectional map
¶
Container that allows to access the elements by key or value. It is implemented using two std::set
, one from A to B and one from B to A. Use get_a(B b)
or get_b(A a)
to get a range view to all the elements matching the specified key.
fresa::BiMap<int, str> map;
//: add elements
map.add(1, "a");
map.add(1, "b");
map.add(1, "c");
map.add(2, "b");
//: get all the elements with key 1
auto a_range = map.get_a(1); // == {"a", "b", "c"}
//: get all the elements with value "b"
auto b_range = map.get_b("b"); // == {1, 2}
//: delete elements with A key 1
map.remove_a(1); // map == {{2, "b"}}
strong types
¶
A reimplementation of rollbear's strong type library. The functionality and usage is mostly the same, but I simplified it a bit for our usecase and used concepts instead of SFINAE for clarity since c++20
is already required for the rest of the engine. All credits go to their implementation.
To describe a strong type alias you do the following:
#include "strong_types.h"
using IntLike = strong::Type<int, decltype([]{}), strong::Regular, ...>;
The first parameter of strong::Type
is the type you are aliasing. The second is a tag, which is an unique template parameter to keep different strong types distinct. We can use the lambda return type for this since it is always guaranteed to be unique, written as decltype([]{})
. Finally, a list of modifiers can be optionally specified. These are passthroughs added to the strong type to allow easier manipulation and functionality.
modifiers
Equality
andEqualityWith<T>
provide equality operators (==
and!=
) with the same type and with another typeT
respectively. This extra type can be strong or regular, but can't be the same type you are defining (for that you can use the regularEquality
).Ordered
andOrderedWith<T>
provide comparison operators (<
,>
,<=
and>=
).Semiregular
is default constructible, move and copy constructible, move and copy assignable and swappable.Regular
extends all semiregular requirements while also being equality comparable. Recommended base type.Unique
makes the type move constructible and assignable but not copy constructible or assignable.Incrementable
provides increment operators (++
and--
). For exclusive increment or decrement usedetail::OnlyIncrementable
anddetail::OnlyDecrementable
.Boolean
gives the type a cast tobool
, for example, to use inside if statements.OStreamable
,IStreamable
andIOStreamable
pass the stream operators (<<
and>>
) to the base type.Arithmetic
andArithmeticWith<T>
provide arithmetic operators (+
,-
,*
,/
and%
), as well as the corresponding assignment operators (+=
,-=
,*=
,/=
and%=
).Bitwise
andBitwiseWith<T>
provide bitwise operators (&
,|
,^
,<<
and>>
), as well as the corresponding assigment operators (&=
,|=
,^=
,<<=
and>>=
).Indexed
allows to access the base type indices using[]
and.at()
.Iterator
adds functionality depending on the iterator type.Range
allows to iterate over the elements. Definesbegin
andend
, as well ascbegin
andcend
. You can use the type inside a range based for loop.ConvertibleTo<T>
gives the type an explicit cast toT
.ImplicitlyConvertibleTo<T>
gives it an implicit cast. Use the latter with caution since it can defeat the whole point of having a separate strong type.Hashable<T>
spetializesstd::hash
to take this strong type. Allows to use it as a key in anstd::unordered_map
. InheritsEquality
.Formattable<T>
spetializesfmt::format
if the library is available. Allows to uselog
to print it.