My C++ sensible prelude.
1# C++ Prelude
2Modern C++ makes me sad when I look at it. So this is my attempt to change
3several of the defaults we use to code in C++. Additionally, several features
4that are missing in the language (but enabled through obscure compiler macros
5and directives) are enabled.
6
7## Includes
8Several containers and algorithms can be included using the `CONTAINERS` or
9`ALGORITHMS` or `IO` directive. I use these very frequently, others might not. For that
10reason they are opt-in. To enable them define the the containers or algorithms
11directive **before** including the header.
12
13Like this:
14```cpp
15#define CONTAINERS
16#define ALGORITHMS
17#define IO
18#include "prelude.hpp"
19```
20The following containers are included:
21 - unordered_map
22 - unordered_set
23 - queue
24 - stack
25 - list
26 - span
27
28The following algorithms are included:
29 - algorithm
30 - ranges
31 - numeric
32
33 The following io headers are included:
34 - format
35 - fstream
36
37The following headers are included by default:
38 - cstdint
39 - limits.h
40 - iostream
41
42## Types
43A mix of the naming scheme in Rust and HolyC is very aesthetically pleasing.
44Types become terse with only the information you need at a first glance.
45Furthermore, for consistency, all types should begin with capital letters.
46
47### Numeric Types
48
49#### Floats
50Most of the floating types that are mentioned in [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754).
51| Prelude | Vanilla |
52|-----------|---------|
53| `BF16` | N/A |
54| `F16` | N/A |
55| `F32` | `float` |
56| `D32` | N/A |
57| `F64` | `double` |
58| `D64` | N/A |
59| `F80` | N/A or `long double` |
60| `D128` | N/A |
61| `F128` | N/A |
62
63Additional information on the [brain floating data type](https://en.wikipedia.org/wiki/Bfloat16_floating-point_format).
64
65| Prelude | Vanilla |
66|-----------|---------|
67| `Size` | `std::size_t` |
68| `Int` | N/A |
69| `U8` | `uint8_t` |
70| `U16` | `uint16_t` |
71| `U32` | `uint32_t` |
72| `U64` | `uint64_t` |
73| `U128` | N/A |
74| `I8` | `int8_t` |
75| `I16` | `int16_t` |
76| `I32` | `int32_t` |
77| `I64` | `int64_t` |
78| `I128` | N/A |
79
80These changes beg the question: What is wrong with `int`?
81Well `int` would be a perfectly valid type if C++ supported Big Integers like
82Java does. The problem is `int` is fixed to a type that is _sometimes_ 32-bits
83and other times something else. I think this is very inelegant and needlessly
84terse. It is better to simply know what kind of integer and bit-width it takes
85up.
86
87The only exception to this rule being `Size` and `Int`. For the former we want
88to preserve the ability to scale depending on what system runs the binary. For
89the latter we want to optimize the CI development cycle, so the less worries the
90better. If you want an `Int` then have an `Int`.
91
92### Other Types
93| Prelude | Vanilla |
94|-----------|---------|
95| `Unit` | `void` |
96| `Void` | N/A |
97| `Bool` | `bool` |
98| `Char` | `char` |
99| `Handle<T>` | `T*` |
100| `Ref<T>` | `T&` |
101| `Vector` | `std::vector<T>` |
102| `String` | `std::string` |
103| `CString` | `const char *` |
104
105In most situations, it is clearer to write the type as a `Handle<T>` than a `T*`.
106Same goes for `Ref<T>`.
107This also remains consistent with the implementation of smart pointers in the
108standard library, for example `std::unique_ptr<T>`.
109
110`std::string_literals` are enabled by default.
111
112## Keywords
113| Prelude | Vanilla |
114|-----------|---------|
115| `let` | `auto const` |
116| `var` | `auto` |
117| `fn` | `[[nodiscard, gnu::const]] auto` |
118| `proc` | `[[nodiscard]] auto` |
119| `ignored = foo();` | `static_cast<void>(foo());` |
120| `fst` | `std::get<0>` |
121| `snd` | `std::get<1>` |
122| `thr` | `std::get<2>` |
123
124You define terms as `let x = 0;`. If you need the term to be of any specific
125numeric type then use suffixes like: `let x = 0u;` (x is unsigned).
126If you need a variable term then use the `var` keyword instead. The motivation
127for this is that you should prefer immutable terms before considering mutable
128variables.
129
130All functions should be `[[nodiscard]]` and be annotated with trailing return
131types.
132If you need to ignore the return value of a function then use `ignore = ...` to
133explicitly ignore it.
134Additionally, the term `fn` (function) should be used in its most literal
135mathematical sense, for a given input it will always produce the same output.
136If you want to do something that requires side effects then use the `proc`
137(procedure) keyword instead.
138
139`fst`, `snd` and `thr` naming conventions are taken from Haskell. The motivation
140being that `std::get<#>` is extremely verbose for accessing the
141first/second/third/etc members of that tuple.
142
143## Better Main
144The way `main` is defined for use is very old. It is not type safe and uses old
145types like c-strings. We can, instead, make a better `Main` that is the one we
146define our program in and forward the arguments in a type-safe manner.
147
148Implementation taken from [C++ Weekly ep 361 by Jason Turner](https://www.youtube.com/watch?v=zCzD9uSDI8c)
149```cpp
150I32 main(const I32 argc, const Handle<const CString> argv) {
151 // declare function inside main
152 proc Main(std::span<const std::string_view>) noexcept -> I32;
153
154 // pass arguments to safe main
155 let args = std::vector<std::string_view>(argv, std::next(argv, static_cast<std::ptrdiff_t>(argc)));
156 return Main(args);
157}
158```
159
160NOTE: It has been mentioned that `argv` has historically been used for passing
161sensitive information like passwords to programs. In that case, the array used
162to store the different arguments is chopped up to remove the sensitive
163information. For now, this method does not allow do that. Furthermore,
164sending sensitive information through command line arguments is a bad idea IMO.
165
166### Short example
167Solution for Rosetta Code "[Balanced Brackets](https://rosettacode.org/wiki/Balanced_brackets)"
168using this prelude.
169```cpp
170#include <format>
171#include <random>
172#include "prelude.hpp"
173
174proc generate(I32 n, Char left = '[', Char right = ']') -> String
175{
176 var rd = std::random_device();
177 var str = String(n, left) + String(n, right);
178 std::shuffle(str.begin(), str.end(), std::mt19937(rd()));
179
180 return str;
181}
182
183fn balanced(std::string_view str, Char left = '[', Char right = ']') -> Bool
184{
185 var count = 0;
186 for (let& ch : str) {
187 if (ch == left)
188 count++;
189 else if (ch == right)
190 if (--count < 0)
191 return false;
192 }
193
194 return count == 0;
195}
196
197proc Main([[maybe_unused]] const std::span<const std::string_view> args) -> I32
198{
199 for (var i = 0; i < 9; ++i) {
200 let s = String(generate(i));
201 ignore = println(
202 std::format(" {}: {}", balanced(s) ? "ok" : "bad", s));
203 }
204}
205```
206
207# TODO:
208- [ ] Add algorithms
209- [ ] Improve range algorithms