My C++ sensible prelude.
at main 209 lines 6.6 kB view raw view rendered
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