Step 7 of 7
Attributes, consteval and constinit
Modern C++ adds small annotations that tell the compiler what you mean, so it can catch mistakes or optimize, without changing what correct code does.
Attributes
Attributes go in double square brackets:
| Attribute | Meaning |
|---|---|
[[nodiscard]] |
warn if a caller ignores the return value (an error code, a newly allocated object) |
[[maybe_unused]] |
don't warn that this variable or parameter is unused |
[[fallthrough]] |
this case deliberately continues into the next one |
[[deprecated("use x")]] |
warn anyone who still calls this |
[[likely]] / [[unlikely]] (C++20) |
a hint about which branch usually runs |
[[nodiscard]] is the one worth adding everywhere it applies. [[nodiscard]] bool save(const Config&); turns save(cfg); (forgetting to check) into a compiler warning.
constexpr, consteval, constinit
constexprfunction: may run at compile time, when its arguments are constants and the result is needed as a constant, and runs at run time otherwise.constevalfunction (C++20): must run at compile time. Calling it with a value only known at run time is a compile error. Use it when a run-time call would be a mistake, like hashing a fixed string or validating a format.constinitvariable (C++20): must be initialized at compile time, but can change later. It's for globals: it guarantees no run-time initialization order problems (the "static initialization order fiasco", where one global's constructor reads another global that isn't set up yet).
#include <iostream>
consteval int kib(int n) { return n * 1024; } // compile time only
constexpr int square(int n) { return n * n; } // either
constinit int buffer_size = kib(4); // set before main runs; still changeable
[[nodiscard]] bool resize(int& size, int wanted) {
if (wanted <= 0) return false;
size = wanted;
return true;
}
int main() {
static_assert(kib(2) == 2048);
int n = 5;
std::cout << square(n) << " " << buffer_size << "\n"; // square at run time here
if (!resize(buffer_size, kib(8))) return 1;
std::cout << buffer_size << "\n";
}
25 4096
8192
Calling kib(n) with the run-time variable n would not compile, and writing resize(buffer_size, 100); without checking the result gets a warning.
The classic use: switching on strings
A switch only works on integers, and each case needs a compile-time constant. A consteval hash turns fixed strings into constants, and a constexpr version of the same hash handles the run-time input.
Your turn: write fnv1a(s) as a constexpr 32-bit FNV-1a hash (start from 2166136261; for each character, XOR in the character as an unsigned char, then multiply by 16777619), id_of(s) as a consteval wrapper around it, and command_code(cmd), which switches on fnv1a(cmd) with case id_of("start"), id_of("stop") and id_of("pause") returning 1, 2 and 3, and -1 for anything else. Mark command_code [[nodiscard]].