Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions content/any.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## What It Does

`std::any` holds a single value of any copy-constructible type, with type-safe access via `std::any_cast()`.
Small objects may be stored inline (small-buffer optimization), larger objects are heap-allocated.

## Why It Matters

`void*` can hold a pointer to any type but provides no type safety and no ownership.
`std::any` owns the stored value, manages its lifetime, and throws `std::bad_any_cast` on type mismatch.

## Example

```cpp
#include <any>
#include <print>
#include <string>

int main() {
auto a = std::any(42); // a is of type std::any
std::println("int: {}", std::any_cast<int>(a));

a = std::string("hello");
std::println("string: {}", std::any_cast<std::string>(a));

try {
std::any_cast<double>(a);
} catch (const std::bad_any_cast& e) {
std::println("bad cast: {}", e.what());
}
}
```
46 changes: 46 additions & 0 deletions content/consteval.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
execute: true
---

## What It Does

`consteval` declares an immediate function that must produce a compile-time constant.
Unlike `constexpr` functions, which may execute at runtime, a `consteval` function is guaranteed
to be evaluated during compilation. Calling a `consteval` function with non-constant arguments is a compile-time error.

## Why It Matters

`constexpr` functions can execute at either compile time or runtime depending on context.
When compile-time evaluation is required (e.g. for computing lookup tables, enforcing compile-time validation, or embedding computed values),
`consteval` enforces that the function is never called at runtime.

## Example

```cpp
#include <print>

consteval int square(int n) {
return n * n;
}

consteval int factorial(int n) {
auto result = 1;

for (int i = 2; i <= n; ++i) {
result *= i;
}

return result;
}

int main() {
constexpr auto sq = square(7);
constexpr auto fact = factorial(10);

// auto x = 5;
// auto y = square(x); // error: x is not a constant expression

std::println("square(7) = {}", sq);
std::println("factorial(10) = {}", fact);
}
```
46 changes: 46 additions & 0 deletions content/constexpr-vector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
execute: true
---

## What It Does

`std::vector` can be used in `constexpr` context: vectors can be created, modified, and destroyed during
constant evaluation.
The compiler's constexpr evaluator handles dynamic memory allocation and deallocation within the evaluation,
provided no allocation persists to runtime.

## Why It Matters

Before this, compile-time collections required `std::array` with a fixed size known in advance, or manual
constexpr-compatible data structures.
`constexpr` `std::vector` allows variable-length computation during constant evaluation using the same
container used at runtime.

## Example

```cpp
#include <print>
#include <vector>

consteval int sum_first_n(int n) {
auto v = std::vector<int>();

for (auto i = 1; i <= n; ++i) {
v.push_back(i);
}

auto total = 0;

for (auto x : v) {
total += x;
}

return total;
}

int main() {
constexpr auto result = sum_first_n(10);

std::println("sum of 1..10 = {}", result);
}
```
41 changes: 41 additions & 0 deletions content/constexpr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
execute: true
---

## What It Does

`constexpr` declares that a function or variable can be evaluated at compile time.
In C++11, constexpr functions are limited to a single return statement (plus `static_assert` and `typedef`),
and constexpr variables must be initialized with constant expressions.
The compiler evaluates constexpr functions when all arguments are constant expressions.

## Why It Matters

Before `constexpr`, compile-time computation required template metaprogramming with recursive template
instantiation. `constexpr` allows writing computation in ordinary function syntax that the compiler
evaluates during compilation, using the same language constructs as runtime code.

## Example

```cpp
#include <print>

constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}

constexpr auto grid_size = 8;
constexpr auto total_cells = grid_size * grid_size;

int main() {
constexpr auto f5 = factorial(5);
static_assert(f5 == 120, "factorial(5) must be 120");

std::println("factorial(5) = {}", f5);
std::println("total cells in {}x{} grid = {}", grid_size, grid_size, total_cells);

// Also callable at runtime
auto n = 7;
std::println("factorial({}) = {}", n, factorial(n));
}
```
51 changes: 51 additions & 0 deletions content/ctad.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
execute: true
---

## What It Does

Class template argument deduction (CTAD) allows the compiler to deduce template arguments for
a class template from constructor arguments.
Deduction guides, either implicit (generated from constructors) or user-defined, control
how constructor arguments map to template parameters.

## Why It Matters

Before CTAD, constructing class templates required explicit template arguments
(e.g. `std::pair<int, double>(1, 2.0)`) or helper functions like `std::make_pair()`.
CTAD allows `std::pair(1, 2.0)` to deduce the template arguments directly from the constructor call.

## Example

```cpp
#include <array>
#include <mutex>
#include <print>
#include <tuple>
#include <vector>

template <typename T>
struct Wrapper {
T value;
Wrapper(T v) : value(v) {}
};

// User-defined deduction guide: string literals deduce as std::string
Wrapper(const char*) -> Wrapper<std::string>;

int main() {
auto p = std::pair(1, 2.5); // std::pair<int, double>
auto t = std::tuple(1, 'a', 3.0); // std::tuple<int, char, double>
auto a = std::array{1, 2, 3, 4}; // std::array<int, 4>

auto mtx = std::mutex();
auto lock = std::lock_guard(mtx); // std::lock_guard<std::mutex>

auto w = Wrapper("hello"); // Wrapper<std::string> via deduction guide

std::println("pair: ({}, {})", p.first, p.second);
std::println("tuple element 0: {}", std::get<0>(t));
std::println("array size: {}", a.size());
std::println("wrapper: {}", w.value);
}
```
41 changes: 41 additions & 0 deletions content/decltype.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
execute: true
---

## What It Does

`decltype(expr)` yields the type of its operand expression **without evaluating it**.
For an identifier or class member access, it gives the declared type.
For other expressions, it produces the type including [value category](https://en.cppreference.com/w/cpp/language/value_category.html): lvalue expressions
yield a reference type, xvalues yield an rvalue reference, and prvalues yield the non-reference type.

## Why It Matters

Before `decltype`, expressing the return type of a function in terms of its parameters or
capturing the type of a complex expression required manual specification or type traits.
`decltype` allows the compiler to deduce the exact type from an expression, which is essential
for generic code and trailing return types.

## Example

```cpp
#include <print>
#include <type_traits>

template <typename A, typename B>
auto multiply(A a, B b) -> decltype(a * b) {
return a * b;
}

int main() {
auto x = 123;
decltype(x) y = 10; // int (declared type of identifier)
decltype((x)) z = x; // int& (lvalue expression)

auto result = multiply(3, 2.5);

std::println("y = {}", y);
std::println("result type is double: {}", std::is_same_v<decltype(result), double>);
std::println("result = {}", result);
}
```
53 changes: 53 additions & 0 deletions content/defaulted-special-members.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
execute: true
---

## What It Does

The compiler can implicitly generate move constructors and move assignment operators that perform
member-wise moves.
`= default` explicitly requests the compiler-generated versions.
These defaulted operations move each non-static data member and base class subobject individually.

## Why It Matters

Before C++11, classes had only copy constructors and copy assignment operators as compiler-generated
special members.
Adding move semantics to a class required manually writing both a move constructor and move assignment operator.
Defaulting them generates member-wise move operations without user-written code.

## Example

```cpp
#include <print>
#include <string>
#include <utility>
#include <vector>

struct Record {
Record(std::string n, std::vector<int> v)
: name(std::move(n))
, values(std::move(v))
{}

// Explicitly defaulted copy operations
Record(const Record&) = default;
Record& operator=(const Record&) = default;

// Explicitly defaulted move operations
Record(Record&&) = default;
Record& operator=(Record&&) = default;

std::string name;
std::vector<int> values;
};

int main() {
auto a = Record("sensor", {1, 2, 3, 4, 5});
std::println("a: name={}, values.size()={}", a.name, a.values.size());

auto b = std::move(a); // uses defaulted move constructor
std::println("b: name={}, values.size()={}", b.name, b.values.size());
std::println("a after move: name={}, values.size()={}", a.name, a.values.size());
}
```
12 changes: 8 additions & 4 deletions content/designated-initializers.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ execute: true
## What It Does

Designated initializers initialize aggregate members by name using `.member = value` syntax.
Members may be omitted from the initializer list and are default-initialized; the syntax explicitly associates each initializer with its corresponding member.
Members may be omitted from the initializer list and are default-initialized; the syntax explicitly
associates each initializer with its corresponding member.

## Why It Matters

Positional initialization is sensitive to member order; adding or reordering members changes the mapping of initializers without diagnostic.
Designated initializers are insensitive to member reordering and document the mapping between initializers and members explicitly.
The syntax is compatible with C99 designated initializers, with the additional constraint that designators must appear in declaration order in C++.
Positional initialization is sensitive to member order; adding or reordering members changes the mapping
of initializers without diagnostic.
Designated initializers are insensitive to member reordering and document the mapping between initializers
and members explicitly.
The syntax is compatible with C99 designated initializers, with the additional constraint that designators
must appear in declaration order in C++.

## Example

Expand Down
46 changes: 46 additions & 0 deletions content/fold-expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
execute: true
---

## What It Does

Fold expressions apply a binary operator to all elements of a parameter pack in a single expression.
The four forms are:
- unary left fold `(... op pack)`
- unary right fold `(pack op ...)`
- binary left fold `(init op ... op pack)`
- binary right fold `(pack op ... op init)`

## Why It Matters

Before fold expressions, expanding a parameter pack with a binary operator required recursive template
instantiation with a base case specialization.
Fold expressions express the reduction directly in a single expression without recursion or helper functions.

## Example

```cpp
#include <print>
#include <string>

template <typename... Args>
auto sum(Args... args) {
return (... + args); // unary left fold
}

template <typename... Args>
bool all_true(Args... args) {
return (true && ... && args); // binary left fold
}

template <typename... Args>
void print_all(Args&&... args) {
((std::println("{}", args)), ...); // unary right fold with comma operator
}

int main() {
std::println("sum: {}", sum(1, 2, 3, 4, 5));
std::println("all_true: {}", all_true(true, true, false));
print_all(42, 3.14, std::string("hello"));
}
```
Loading
Loading