diff --git a/content/any.md b/content/any.md new file mode 100644 index 0000000..b86a93e --- /dev/null +++ b/content/any.md @@ -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 +#include +#include + +int main() { + auto a = std::any(42); // a is of type std::any + std::println("int: {}", std::any_cast(a)); + + a = std::string("hello"); + std::println("string: {}", std::any_cast(a)); + + try { + std::any_cast(a); + } catch (const std::bad_any_cast& e) { + std::println("bad cast: {}", e.what()); + } +} +``` diff --git a/content/consteval.md b/content/consteval.md new file mode 100644 index 0000000..4c4f1ab --- /dev/null +++ b/content/consteval.md @@ -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 + +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); +} +``` diff --git a/content/constexpr-vector.md b/content/constexpr-vector.md new file mode 100644 index 0000000..7c533d8 --- /dev/null +++ b/content/constexpr-vector.md @@ -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 +#include + +consteval int sum_first_n(int n) { + auto v = std::vector(); + + 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); +} +``` diff --git a/content/constexpr.md b/content/constexpr.md new file mode 100644 index 0000000..776c656 --- /dev/null +++ b/content/constexpr.md @@ -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 + +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)); +} +``` diff --git a/content/ctad.md b/content/ctad.md new file mode 100644 index 0000000..22172cb --- /dev/null +++ b/content/ctad.md @@ -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(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 +#include +#include +#include +#include + +template +struct Wrapper { + T value; + Wrapper(T v) : value(v) {} +}; + +// User-defined deduction guide: string literals deduce as std::string +Wrapper(const char*) -> Wrapper; + +int main() { + auto p = std::pair(1, 2.5); // std::pair + auto t = std::tuple(1, 'a', 3.0); // std::tuple + auto a = std::array{1, 2, 3, 4}; // std::array + + auto mtx = std::mutex(); + auto lock = std::lock_guard(mtx); // std::lock_guard + + auto w = Wrapper("hello"); // Wrapper 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); +} +``` diff --git a/content/decltype.md b/content/decltype.md new file mode 100644 index 0000000..be4c15d --- /dev/null +++ b/content/decltype.md @@ -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 +#include + +template +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); + std::println("result = {}", result); +} +``` diff --git a/content/defaulted-special-members.md b/content/defaulted-special-members.md new file mode 100644 index 0000000..3b2c833 --- /dev/null +++ b/content/defaulted-special-members.md @@ -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 +#include +#include +#include + +struct Record { + Record(std::string n, std::vector 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 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()); +} +``` diff --git a/content/designated-initializers.md b/content/designated-initializers.md index 774217d..22d2475 100644 --- a/content/designated-initializers.md +++ b/content/designated-initializers.md @@ -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 diff --git a/content/fold-expressions.md b/content/fold-expressions.md new file mode 100644 index 0000000..ccaf5bc --- /dev/null +++ b/content/fold-expressions.md @@ -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 +#include + +template +auto sum(Args... args) { + return (... + args); // unary left fold +} + +template +bool all_true(Args... args) { + return (true && ... && args); // binary left fold +} + +template +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")); +} +``` diff --git a/content/guaranteed-copy-elision.md b/content/guaranteed-copy-elision.md new file mode 100644 index 0000000..6783fc0 --- /dev/null +++ b/content/guaranteed-copy-elision.md @@ -0,0 +1,36 @@ +--- +execute: true +--- + +## What It Does + +In C++17 and newer, returning a [prvalue](https://en.cppreference.com/w/cpp/language/value_category.html) of the same type as the function return type, +or initializing an object from a prvalue, is guaranteed to construct the object directly in its destination. +No copy or move constructor is called or required to exist. + +## Why It Matters + +Before C++17, copy elision was an optimization that compilers were permitted but not required to perform. +Code relying on elision could fail to compile if the copy/move constructor was deleted or inaccessible. +C++17 mandates elision in these cases, making prvalue semantics part of the language definition. + +## Example + +```cpp +#include + +struct Widget { + Widget() { std::println("constructed"); } + Widget(const Widget&) = delete; + Widget(Widget&&) = delete; +}; + +Widget make() { + return Widget(); +} + +int main() { + auto w = make(); + std::println("done"); +} +``` diff --git a/content/initializer-lists.md b/content/initializer-lists.md new file mode 100644 index 0000000..67a8528 --- /dev/null +++ b/content/initializer-lists.md @@ -0,0 +1,61 @@ +--- +execute: true +--- + +## What It Does + +`std::initializer_list` allows functions and constructors to accept brace-enclosed lists of values. +Brace initialization (`{}`) can construct objects, initialize containers, and pass argument lists. +The initializer list references a temporary array created by the compiler. + +## Why It Matters + +Before initializer lists, constructing a container with specific values required repeated `push_back()` +calls or initialization from a pre-existing array. + +Brace initialization allows direct construction from a list of values in a single expression. + +## Example + +```cpp +#include +#include +#include +#include + +int sum(std::initializer_list values) { + auto total = 0; + + for (auto v : values) { + total += v; + } + + return total; +} + +class Sensor { +public: + Sensor(std::string name, std::initializer_list readings) + : name_(std::move(name)) + , readings_(readings) + {} + + void report() const { + std::println("sensor '{}' has {} readings", name_, readings_.size()); + } + +private: + std::string name_; + std::vector readings_; +}; + +int main() { + auto v = std::array{10, 20, 30, 40}; + std::println("vector size: {}", v.size()); + + std::println("sum: {}", sum({1, 2, 3, 4, 5})); + + auto s = Sensor("temperature", {36.5, 37.0, 36.8}); + s.report(); +} +``` diff --git a/content/inline-variables.md b/content/inline-variables.md new file mode 100644 index 0000000..a118150 --- /dev/null +++ b/content/inline-variables.md @@ -0,0 +1,35 @@ +--- +execute: true +--- + +## What It Does + +The `inline` specifier can be applied to variables, giving them the same linkage semantics as inline functions. +An inline variable can be defined in a header and included in multiple translation units without causing +multiple-definition errors. The linker merges all definitions into a single instance. + +## Why It Matters + +Before inline variables, defining a constant or static data member in a header required a separate definition +in exactly one source file, or workarounds like function-local statics or template tricks. +Inline variables allow header-only definitions of global constants and static data members with +guaranteed single initialization. + +## Example + +```cpp +#include + +struct Config { + static inline int max_retries = 3; + static inline const char* app_name = "game"; +}; + +inline constexpr double pi = 3.14159265358979323846; + +int main() { + std::println("app: {}", Config::app_name); + std::println("max retries: {}", Config::max_retries); + std::println("pi: {}", pi); +} +``` diff --git a/content/literal-suffix-size-t.md b/content/literal-suffix-size-t.md new file mode 100644 index 0000000..505f6c9 --- /dev/null +++ b/content/literal-suffix-size-t.md @@ -0,0 +1,36 @@ +--- +execute: true +--- + +## What It Does + +The literal suffixes `uz` (or `UZ`) and `z` (or `Z`) produce values of type `size_t` and the corresponding +signed type (`ptrdiff_t` or the signed counterpart of `ssize_t`), respectively. +These suffixes apply to integer literals, complementing the existing `u`, `l`, `ll`, and other suffixes. + +## Why It Matters + +Before these suffixes, writing a loop counter or variable of type `size_t` from a literal required a cast +or a separate declaration. +Comparing a signed literal like `0` with `container.size()` produced signed/unsigned mismatch warnings. +The `uz` and `z` suffixes allow `size_t` and its signed counterpart to be expressed directly as literals. + +## Example + +```cpp +#include +#include + +int main() { + auto v = std::vector{10, 20, 30, 40, 50}; + + // uz suffix: literal is size_t, no signed/unsigned mismatch + for (auto i = 0uz; i < v.size(); ++i) { + std::println("v[{}] = {}", i, v[i]); + } + + // z suffix: signed counterpart of size_t + auto offset = -1z; + std::println("offset value: {}, type size: {} bytes", offset, sizeof(offset)); +} +``` diff --git a/content/make-unique.md b/content/make-unique.md index 3595a17..5b75dba 100644 --- a/content/make-unique.md +++ b/content/make-unique.md @@ -4,11 +4,16 @@ execute: true ## What It Does -`std::make_unique()` constructs a `std::unique_ptr` by allocating storage, constructing the object in-place, and wrapping the pointer in a `unique_ptr` in a single operation. Arguments are forwarded to the object's constructor. +`std::make_unique()` constructs a `std::unique_ptr` by allocating storage, constructing the object +in-place, and wrapping the pointer in a `std::unique_ptr` in a single operation. +Arguments are forwarded to the object's constructor. ## Why It Matters -Prior to `make_unique`, `std::unique_ptr(new T(...))` was the typical pattern. When multiple allocations occur in a single expression and one throws an exception, memory leaks occur because the corresponding `unique_ptr` destructor is not yet responsible for the allocated object. `make_unique` eliminates this exception-safety gap while reducing verbosity. +Prior to `std::make_unique()`, `std::unique_ptr(new T(...))` was the typical pattern. +When multiple allocations occur in a single expression and one throws an exception, memory leaks occur +because the corresponding `std::unique_ptr` destructor is not yet responsible for the allocated object. +`std::make_unique()` eliminates this exception-safety gap while reducing verbosity. ## Example @@ -18,9 +23,13 @@ Prior to `make_unique`, `std::unique_ptr(new T(...))` was the typical pattern #include struct Player { + Player(std::string n, int s) + : name(std::move(n)) + , score(s) + {} + std::string name; int score; - Player(std::string n, int s) : name(std::move(n)), score(s) {} }; int main() { @@ -32,7 +41,7 @@ int main() { auto numbers = std::make_unique(10); // array of 10 ints numbers[0] = 42; - // Compare to raw new (don't do this) + // Raw new (not recommended since C++14): // std::unique_ptr p(new Player("Bob", 50)); // No explicit delete needed - RAII handles it diff --git a/content/noexcept.md b/content/noexcept.md new file mode 100644 index 0000000..b9cbc23 --- /dev/null +++ b/content/noexcept.md @@ -0,0 +1,54 @@ +--- +execute: true +--- + +## What It Does + +The `noexcept` specifier declares that a function does not throw exceptions. +`noexcept(expr)` conditionally applies based on a compile-time boolean expression. +The `noexcept` operator tests whether an expression is declared non-throwing, +returning a `bool` usable in compile-time contexts. + +## Why It Matters + +Before `noexcept`, the dynamic exception specification `throw()` had runtime overhead: +violating it called `std::unexpected` rather than enabling compiler optimizations. +`noexcept` allows the compiler and user code to omit exception handling infrastructure +entirely for marked functions. + +For example, standard containers like `std::vector` query `noexcept` on move constructors to decide +between moving and copying during reallocation. + +**Note**: When a noexcept function throws an exception, `std::terminate()` is called immediately. +The runtime does not attempt to propagate the exception up the call stack. +Instead, it is treated as a fatal, unrecoverable error. + +## Example + +```cpp +#include +#include +#include + +struct Widget { + Widget() = default; + Widget(Widget&&) noexcept = default; + Widget(const Widget&) = default; +}; + +template +T add(T a, T b) noexcept(std::is_nothrow_move_constructible_v) { + return a + b; +} + +int main() { + std::println("Widget move is noexcept: {}", + std::is_nothrow_move_constructible_v); + + std::println("add is noexcept: {}", + noexcept(add(1, 2))); + + std::println("add> is noexcept: {}", + noexcept(add(std::vector{}, std::vector{}))); +} +``` diff --git a/content/nullptr.md b/content/nullptr.md new file mode 100644 index 0000000..127fb4b --- /dev/null +++ b/content/nullptr.md @@ -0,0 +1,53 @@ +--- +execute: true +--- + +## What It Does + +`nullptr` is a keyword of type `std::nullptr_t` that represents a null pointer. +It is implicitly convertible to any pointer type but not to integral types. +`std::nullptr_t` can be used as a function parameter type to accept a `nullptr` value. + +## Why It Matters + +The `NULL` macro is defined as an integer constant (typically `0`), which causes ambiguity when +overloading functions that accept both pointer and integer parameters. +`nullptr` is unambiguously a pointer value, so overload resolution selects the pointer +overload without ambiguity. + +## Example + +```cpp +#include +#include + +void process(int value) { + std::println("process(int): {}", value); +} + +void process(int* ptr) { + if (ptr) { + std::println("process(int*): pointing to {}", *ptr); + } else { + std::println("process(int*): null pointer"); + } +} + +void accepts_any_null(std::nullptr_t) { + std::println("received a null pointer value"); +} + +int main() { + process(0); // calls process(int) + process(nullptr); // calls process(int*) + + auto x = 42; + auto* p = &x; + process(p); // calls process(int*) with non-null pointer + + p = nullptr; // assign null to pointer + process(p); // calls process(int*) with null pointer + + accepts_any_null(nullptr); +} +``` diff --git a/content/optional.md b/content/optional.md index 05a2255..55a04df 100644 --- a/content/optional.md +++ b/content/optional.md @@ -4,15 +4,16 @@ execute: true ## What It Does -`std::optional` is a class template containing either a value of type `T` or no value. +`std::optional` is a class template containing either a value of type `T` or no value. It provides a type-safe representation of an optional value without pointer semantics, -sentinel values, or output parameters. Member functions `has_value()` and `value()` provide -value checking and access, with `value()` throwing `std::bad_optional_access` if no value is present. +sentinel values, or output parameters. +Member functions `has_value()` and `value()` provide value checking and access, with `value()` +throwing `std::bad_optional_access` if no value is present. ## Why It Matters Nullable pointers shift null-check responsibility to each use site without compile-time enforcement. -Sentinel values overload the value domain of a type and require convention-based interpretation. +Sentinel values overload the value domain of a type and require convention-based interpretation (e.g. `std::string::npos`). `std::optional` encodes value presence or absence in the type itself, enabling static analysis and compile-time verification of access patterns. @@ -27,8 +28,7 @@ and compile-time verification of access patterns. std::optional find_user(int id) { static auto users = std::map{{1, "Alice"}, {2, "Bob"}}; - auto it = users.find(id); - if (it != users.end()) { + if (auto it = users.find(id); it != users.end()) { return it->second; } @@ -45,6 +45,7 @@ int main() { auto maybe_number = std::optional(); std::println("Has value: {}", maybe_number.has_value()); // false + maybe_number = 42; std::println("Value: {}", *maybe_number); // 42 } diff --git a/content/override-final.md b/content/override-final.md new file mode 100644 index 0000000..0fc714e --- /dev/null +++ b/content/override-final.md @@ -0,0 +1,50 @@ +--- +execute: true +--- + +## What It Does + +`override` explicitly marks a member function as overriding a virtual function in a base class; +the compiler issues an error if no matching base function exists. +`final` prevents further overriding of a virtual function or derivation from a class. + +## Why It Matters + +Without `override`, a signature mismatch (wrong parameter type, missing `const`) silently declares a +new function instead of overriding the base version. +`override` converts this silent error into a compile-time diagnostic. +`final` allows a class hierarchy to close extension points explicitly. + +## Example + +```cpp +#include + +struct Base { + virtual int compute(int x) const { return x; } + virtual ~Base() = default; +}; + +struct Derived : Base { + int compute(int x) const override { return x * 2; } + // int compute(double x) const override; // error: no matching base function +}; + +struct Final : Derived { + int compute(int x) const final { return x * 3; } +}; + +// struct Beyond : Final { +// int compute(int x) const override; // error: overriding final function +// }; + +int main() { + auto d = Derived(); + Base& ref = d; + std::println("Derived::compute(5) = {}", ref.compute(5)); // 10 + + auto f = Final(); + Base& ref2 = f; + std::println("Final::compute(5) = {}", ref2.compute(5)); // 15 +} +``` diff --git a/content/parallel-algorithms.md b/content/parallel-algorithms.md new file mode 100644 index 0000000..511cbbc --- /dev/null +++ b/content/parallel-algorithms.md @@ -0,0 +1,36 @@ +--- +execute: false +--- + +## What It Does + +Standard algorithms in ``, ``, and `` accept an execution policy as their first argument. +`std::execution::seq` runs sequentially, `std::execution::par` allows parallel execution across threads, and +`std::execution::par_unseq` allows both parallel and vectorized execution. + +## Why It Matters + +Before execution policies, parallelizing standard algorithms required manual thread management or +third-party libraries. +Execution policies allow existing algorithm calls to opt into parallel execution by adding a single argument. + +## Example + +```cpp +#include +#include +#include +#include +#include + +int main() { + auto v = std::vector(1000); + std::iota(v.begin(), v.end(), 1); + + std::sort(std::execution::par, v.begin(), v.end(), std::greater{}); + std::println("largest: {}", v.front()); + + auto sum = std::reduce(std::execution::par, v.begin(), v.end(), 0LL); + std::println("sum: {}", sum); +} +``` diff --git a/content/range-for.md b/content/range-for.md new file mode 100644 index 0000000..c4e9e82 --- /dev/null +++ b/content/range-for.md @@ -0,0 +1,54 @@ +--- +execute: true +--- + +## What It Does + +`for (auto& x : container)` iterates over all elements of a range. +The compiler expands it to a begin/end iterator loop. +It works with arrays, standard containers, and any type that provides `begin()` and `end()` +member functions or free functions found via [ADL](https://en.cppreference.com/w/cpp/language/adl.html). + +## Why It Matters + +Before range-for, iterating required explicit iterator type declarations or index-based loops +with manual bounds management. +Range-for provides iteration without exposing iterator types or requiring the user to specify +container sizes or end conditions. + +## Example + +```cpp +#include +#include +#include +#include + +int main() { + auto numbers = std::vector{10, 20, 30, 40, 50}; + + for (const auto& n : numbers) { + std::println("{}", n); + } + + // Works with built-in arrays + int arr[] = {1, 2, 3}; + auto total = 0; + + for (auto x : arr) { + total += x; + } + + std::println("array sum = {}", total); + + // With structured bindings over a map + auto ages = std::map{ + {std::string_view("Alice"), 30}, + {std::string_view("Bob"), 25} + }; + + for (const auto& [name, age] : ages) { + std::println("{} is {}", name, age); + } +} +``` diff --git a/content/rvalue-references.md b/content/rvalue-references.md new file mode 100644 index 0000000..7219934 --- /dev/null +++ b/content/rvalue-references.md @@ -0,0 +1,72 @@ +--- +execute: true +--- + +## What It Does + +Rvalue references (`T&&`) distinguish temporary objects from lvalues. +They enable move semantics, allowing resources to be transferred from expiring objects instead of copied. +`std::move()` casts an lvalue to an rvalue reference, indicating that its resources may be taken. + +## Why It Matters + +Before rvalue references, passing or returning large objects always involved copying, which requires +allocating new resources and deallocating the originals. +Move semantics allow resource transfer from temporaries, avoiding the allocation and deallocation +overhead associated with deep copies. + +## Example + +```cpp +#include +#include +#include + +class Buffer { +public: + Buffer(std::string s) + : data_(std::move(s)) + {} + + // Move constructor: transfers resources from other + Buffer(Buffer&& other) noexcept + : data_(std::move(other.data_)) + { + std::println("move constructor called"); + } + + // Move assignment: transfers resources from other + Buffer& operator=(Buffer&& other) noexcept + { + std::println("move assignment called"); + + // prevent self-assignment, e.g. buff = std::move(buff) + if (std::addressof(other) != this) { + // move over the data members + data_ = std::move(other.data_); + } + + return *this; + } + + Buffer(const Buffer&) = default; + Buffer& operator=(const Buffer&) = default; + + const std::string& data() const { return data_; } + +private: + std::string data_; +}; + +int main() { + auto a = Buffer("hello world"); + auto b = std::move(a); // invokes move constructor + + std::println("b.data() = {}", b.data()); + + auto c = Buffer("temporary"); + c = std::move(b); // invokes move assignment + + std::println("c.data() = {}", c.data()); +} +``` diff --git a/content/scoped-enums.md b/content/scoped-enums.md new file mode 100644 index 0000000..d5b6f83 --- /dev/null +++ b/content/scoped-enums.md @@ -0,0 +1,50 @@ +--- +execute: true +--- + +## What It Does + +`enum class` (scoped enumeration) defines enumerators that are scoped to the enum type and do not +implicitly convert to integers. The underlying type can be explicitly specified. +Accessing an enumerator requires the enum name as a qualifier. + +## Why It Matters + +Traditional unscoped enums leak their enumerator names into the enclosing scope and implicitly convert +to `int`, permitting unintended arithmetic and comparisons between unrelated enum types. +Scoped enums restrict both name visibility and implicit conversions, requiring `static_cast` +for integer conversion. + +## Example + +```cpp +#include +#include +#include + +enum class Color : uint8_t { + Red, + Green, + Blue +}; + +enum class Permission : unsigned { + Read = 1, + Write = 2, + Execute = 4 +}; + +int main() { + auto c = Color::Green; + // int x = c; // error: no implicit conversion + + auto underlying = static_cast(c); + std::println("Color::Green underlying value: {}", underlying); + + auto p = Permission::Read; + std::println("Permission::Read underlying value: {}", static_cast(p)); + + // With C++23's std::to_underlying(): + std::println("With std::to_underlying: {}", std::to_underlying(p)); +} +``` diff --git a/content/string-view.md b/content/string-view.md new file mode 100644 index 0000000..5444bc0 --- /dev/null +++ b/content/string-view.md @@ -0,0 +1,44 @@ +--- +execute: true +--- + +## What It Does + +`std::string_view` is a non-owning reference to a contiguous character sequence. +It holds a pointer and a length, providing read-only access to string data without allocation. +It can refer to `std::string`, string literals, or substrings. + +## Why It Matters + +Functions accepting `const std::string&` require callers with C strings or substrings to construct a +temporary `std::string`, which allocates memory. +`std::string_view` accepts any contiguous character range without allocation. + +**Note**: Passing a `std::string_view` does not guarantee to point to a null-terminated string. +This matters, for example, when calling a C API function that expects a `const char*`. +In such a case, passing the value of `.data()` to such a function may cause an out-of-bounds violation. + +A workaround is to construct a temporary `std::string`, and passing that string's `.c_str()` to the C function. + +For C++29, a new type `std::zstring_view` (P3655) is planned, which guarantees a null-terminated string. + +## Example + +```cpp +#include +#include +#include + +void greet(std::string_view name) { + std::println("Hello, {}!", name); +} + +int main() { + greet("world"); + + auto s = std::string("Alice"); + greet(s); + + greet(std::string_view(s).substr(0, 3)); +} +``` diff --git a/content/template-aliases.md b/content/template-aliases.md new file mode 100644 index 0000000..be2200a --- /dev/null +++ b/content/template-aliases.md @@ -0,0 +1,41 @@ +--- +execute: true +--- + +## What It Does + +`template using Name = ...;` creates a template alias, a new name for an existing template or +type expression parameterized by template arguments. +Unlike `typedef`, alias templates can be templated and participate in template argument deduction. + +## Why It Matters + +`typedef` cannot be parameterized by template arguments. +Creating a type alias that depends on a template parameter required a wrapper struct with a nested +`type` member and the `typename` keyword at each use site. +Template aliases provide direct parameterized type naming without wrapper structs. + +## Example + +```cpp +#include +#include +#include +#include + +template +using Vec = std::vector; + +template +using StringMap = std::map; + +int main() { + auto numbers = Vec{1, 2, 3}; + std::println("numbers: {:}", numbers); + + auto prices = StringMap(); + prices["apple"] = 1.50; + prices["orange"] = 1.20; + std::println("prices: {:}", prices); +} +``` diff --git a/content/to-chars.md b/content/to-chars.md new file mode 100644 index 0000000..fb8e5f4 --- /dev/null +++ b/content/to-chars.md @@ -0,0 +1,38 @@ +--- +execute: true +--- + +## What It Does + +`std::to_chars()` and `std::from_chars()` in `` convert between numeric values and character sequences. +They perform no memory allocation, take no locale into account, and do not write a null terminator. +They return a result struct indicating success and the position past the last written/read character. + +## Why It Matters + +`std::stoi()`/`std::stod()` and `std::to_string()` perform locale-dependent formatting and may allocate memory. +`sprintf()`/`sscanf()` require format strings and null-terminated buffers. +`to_chars()`/`from_chars()` provide locale-independent, allocation-free conversion with explicit error reporting. + +## Example + +```cpp +#include +#include +#include +#include + +int main() { + char buf[32]; + auto [ptr, ec] = std::to_chars(buf, buf + sizeof(buf), 3.14159); + std::println("to_chars: {}", std::string_view(buf, ptr)); + + auto val = 0.0; // double + auto input = std::string_view("2.71828"); + auto [p, e] = std::from_chars(input.data(), input.data() + input.size(), val); + + if (e == std::errc{}) { + std::println("from_chars: {}", val); + } +} +``` diff --git a/content/using-enum.md b/content/using-enum.md new file mode 100644 index 0000000..724a319 --- /dev/null +++ b/content/using-enum.md @@ -0,0 +1,39 @@ +--- +execute: true +--- + +## What It Does + +`using enum EnumType;` introduces all enumerators of a scoped enumeration into the current scope, +allowing them to be used without the enum type prefix. +Individual enumerators can also be introduced with `using EnumType::enumerator;`. + +## Why It Matters + +Scoped enumerations require the type prefix on every use (e.g., `Color::Red`). +In contexts like switch statements where the enum type is known, the repeated prefix adds verbosity +without disambiguation. `using enum` removes the prefix within a limited scope. + +## Example + +```cpp +#include +#include + +enum class Color { Red, Green, Blue }; + +std::string_view to_string(Color c) { + switch (c) { + using enum Color; + case Red: return "red"; + case Green: return "green"; + case Blue: return "blue"; + } + + return "unknown"; +} + +int main() { + std::println("{}", to_string(Color::Green)); +} +``` diff --git a/content/variadic-templates.md b/content/variadic-templates.md new file mode 100644 index 0000000..83914b1 --- /dev/null +++ b/content/variadic-templates.md @@ -0,0 +1,45 @@ +--- +execute: true +--- + +## What It Does + +Variadic templates accept an arbitrary number of template parameters using `...` pack syntax. +Parameter packs can be expanded in various contexts including function arguments, base class lists, and initializer lists. +Fold expressions (C++17) or recursive instantiation consume the pack elements. + +## Why It Matters + +Before variadic templates, handling a variable number of template arguments required a fixed set of overloads +or preprocessor macros, neither of which provided type safety across all arguments. +Variadic templates allow a single definition to handle any number of arguments with full type checking at +each position. + +## Example + +```cpp +#include +#include + +// Recursive base case +void print_all() {} + +// Recursive variadic function +template +void print_all(const T& first, const Args&... rest) { + std::println("{}", first); + print_all(rest...); +} + +// Variadic sum using fold expression (C++17) +template +auto sum(Args... args) { + return (args + ...); +} + +int main() { + print_all(1, 3.14, std::string("hello"), 'c'); + + std::println("sum = {}", sum(1, 2, 3, 4, 5)); +} +``` diff --git a/content/variant.md b/content/variant.md new file mode 100644 index 0000000..5e16a61 --- /dev/null +++ b/content/variant.md @@ -0,0 +1,45 @@ +--- +execute: true +--- + +## What It Does + +`std::variant` is a type-safe [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that holds a +value of one of its alternative types at any time. +Access is through `std::get()`, `std::get_if()`, or `std::visit()`. +It stores the value in-place without heap allocation. + +## Why It Matters + +C unions do not track which member is active and permit reading the wrong member. +`std::variant` tracks the active alternative and throws `std::bad_variant_access` +(or returns `nullptr` via `get_if()`) on type mismatch. + +## Example + +```cpp +#include +#include +#include + +int main() { + auto v = std::variant(42); + + // Direct access (throws on type mismatch): + std::println("int: {}", std::get(v)); + + v = std::string("hello"); + + // Access using std::visit: + std::visit([](const auto& val) { + std::println("value: {}", val); + }, v); + + // Access using get_if: + if (const auto* p = std::get_if(&v)) { + std::println("double: {}", *p); + } else { + std::println("not a double"); + } +} +``` diff --git a/features_cpp11.yaml b/features_cpp11.yaml index 5a00c1b..e0c39cc 100644 --- a/features_cpp11.yaml +++ b/features_cpp11.yaml @@ -49,6 +49,7 @@ features: - desc: "[`auto`](https://cppreference.com/w/cpp/language/auto.html)" paper: N1984 + content: auto.md support: - GCC 4.4 - Clang @@ -79,6 +80,7 @@ features: - desc: "`constexpr`" paper: N2235 + content: constexpr.md support: - GCC 4.6 - Clang 3.1 @@ -117,6 +119,7 @@ features: - desc: "[Template aliases](https://cppreference.com/w/cpp/language/type_alias.html)" paper: N2258 + content: template-aliases.md support: - GCC 4.7 - Clang 3 @@ -152,6 +155,7 @@ features: - desc: "[Strongly-typed `enum`](https://cppreference.com/w/cpp/language/enum.html#Scoped_enumerations)" paper: N2347 + content: scoped-enums.md support: - GCC 4.4 - Clang 2.9 @@ -168,6 +172,7 @@ features: - desc: "`nullptr`" paper: N2431 + content: nullptr.md support: - GCC 4.6 - Clang 2.9 @@ -264,6 +269,7 @@ features: - desc: "[Variadic templates](https://cppreference.com/w/cpp/language/parameter_pack.html)" paper: [N2242, N2555] + content: variadic-templates.md support: - GCC 4.4 - Clang 2.9 @@ -324,6 +330,7 @@ features: - desc: "[Initializer lists](https://cppreference.com/w/cpp/language/list_initialization.html)" paper: N2672 + content: initializer-lists.md support: - GCC 4.4 - Clang 3.1 @@ -388,6 +395,7 @@ features: - desc: "[Rvalue references](https://cppreference.com/w/cpp/language/reference.html#Rvalue_references)" paper: [N2118, N2844, CWG1138] + content: rvalue-references.md support: - GCC 4.3 (partial) - GCC 4.5 @@ -401,6 +409,7 @@ features: - desc: "[Lambda expressions](https://cppreference.com/w/cpp/language/lambda.html)" paper: [N2550, N2658, N2927] + content: lambdas.md support: - GCC 4.5 - Clang 3.1 @@ -413,6 +422,7 @@ features: - desc: "[Range-for loop](https://cppreference.com/w/cpp/language/range-for.html)" paper: [N2930, N3271] + content: range-for.md support: - GCC 4.6 - Clang 3 @@ -424,6 +434,7 @@ features: - desc: "`noexcept`" paper: N3050 + content: noexcept.md support: - GCC 4.6 - Clang 3 @@ -435,6 +446,7 @@ features: - desc: "Defaulted move [special member functions](https://cppreference.com/w/cpp/language/member_functions.html#Special_member_functions)" paper: N3053 + content: defaulted-special-members.md support: - GCC 4.6 - Clang 3 @@ -446,6 +458,7 @@ features: - desc: "`override` and `final`" paper: [N2928, N3206, N3272] + content: override-final.md support: - GCC 4.7 - Clang 2.9 @@ -455,6 +468,7 @@ features: - desc: "`decltype`" paper: [N2343, N3276] + content: decltype.md support: - GCC 4.3 (partial) - GCC 4.8.1 diff --git a/features_cpp14.yaml b/features_cpp14.yaml index 63a2e42..28988c8 100644 --- a/features_cpp14.yaml +++ b/features_cpp14.yaml @@ -54,6 +54,7 @@ features: - desc: "[Generic lambda expressions](https://cppreference.com/w/cpp/language/lambda.html#Explanation)" paper: N3649 + content: generic-lambdas.md support: - GCC 4.9 - Clang 3.4 @@ -242,6 +243,7 @@ features: - desc: "`std::make_unique()`" paper: N3656 + content: make-unique.md lib: true support: - GCC 4.9 diff --git a/features_cpp17.yaml b/features_cpp17.yaml index 5bc2b02..3ebb419 100644 --- a/features_cpp17.yaml +++ b/features_cpp17.yaml @@ -156,6 +156,7 @@ features: - desc: "Folding expressions" paper: N4295 summary: "Reduce parameter packs with operators." + content: fold-expressions.md support: - GCC 6 - Clang 3.6 @@ -277,6 +278,7 @@ features: - desc: "Guaranteed copy elision" paper: P0135 summary: "Certain copies must be elided, not just allowed." + content: guaranteed-copy-elision.md support: - GCC 7 - Clang 4 @@ -326,6 +328,7 @@ features: - desc: "`constexpr if` statements" paper: P0292 summary: "Compile-time conditional compilation within functions." + content: if-constexpr.md support: - GCC 7 - Clang 3.9 @@ -347,6 +350,7 @@ features: - desc: "`inline` variables" paper: P0386 summary: "Variables can be `inline`, allowing definition in headers." + content: inline-variables.md support: - GCC 7 - Clang 3.9 @@ -389,6 +393,7 @@ features: - desc: "Template argument deduction for class templates ([CTAD](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction.html))" paper: P0091 summary: "Deduce template arguments from constructor arguments." + content: ctad.md support: - GCC 7 - Clang 5 @@ -590,6 +595,7 @@ features: - desc: "The parallelism TS should be standardized (parallel algorithms)" paper: P0024 + content: parallel-algorithms.md lib: true support: - GCC 9 @@ -743,6 +749,7 @@ features: - desc: "`std::string_view`" paper: [N3921, P0220, P0254, P0403] summary: "Non-owning reference to a character sequence." + content: string-view.md lib: true support: - GCC 7 @@ -755,6 +762,7 @@ features: - desc: "`std::any`" paper: [P0220, P0032] + content: any.md lib: true support: - GCC 7 @@ -768,6 +776,7 @@ features: - desc: "`std::optional`" paper: P0220 summary: "A wrapper that may or may not hold a value." + content: optional.md lib: true support: - GCC 7 @@ -812,6 +821,7 @@ features: - desc: "`std::variant`" paper: P0088 summary: "Type-safe discriminated union." + content: variant.md lib: true support: - GCC 7 @@ -901,6 +911,7 @@ features: - desc: "Elementary string conversions (`std::to_chars()` and `std::from_chars()`)" paper: P0067 + content: to-chars.md lib: true support: [GCC 8 (partial), GCC 11, Clang 7 (partial), Clang 14 (partial), Clang 20 (partial), MSVC 14.14 (partial), MSVC 14.15 (partial), MSVC 14.16 (partial), MSVC 14.24, Xcode 10 (partial), Xcode 14.3 (partial)] hints: @@ -977,6 +988,7 @@ features: - desc: "File system library (`std::filesystem` and ``)" paper: [P0218, P0219] summary: "Standard library for file and directory operations." + content: filesystem.md lib: true support: - GCC 8 diff --git a/features_cpp20.yaml b/features_cpp20.yaml index c59ae1f..2c1ec91 100644 --- a/features_cpp20.yaml +++ b/features_cpp20.yaml @@ -42,6 +42,7 @@ features: - desc: "Designated initializers" paper: P0329 summary: "Initialize aggregate members by name." + content: designated-initializers.md support: - GCC 4.7 (partial) - GCC 8 @@ -81,6 +82,7 @@ features: - desc: "Concepts" paper: P0734 summary: "Concepts are predicates that constrain template arguments. They provide clear error messages and enable function overloading based on type properties." + content: concepts.md support: - GCC 5 (partial) - GCC 10 @@ -411,6 +413,7 @@ features: - desc: "Attributes `[[likely]]` and `[[unlikely]]`" paper: P0479 summary: "Hint to the compiler about branch probability." + content: likely-unlikely.md support: - GCC 9 - Clang 12 @@ -473,6 +476,7 @@ features: - desc: "`std::span`" paper: P0122 + content: span.md lib: true support: - GCC 10 @@ -549,6 +553,7 @@ features: - desc: "Allowing virtual function calls in constant expressions" paper: P1064 + content: constexpr-virtual.md support: - GCC 9 - Clang 9 @@ -609,6 +614,7 @@ features: - desc: "`std::bit_cast()`" paper: P0476 + content: bit-cast.md lib: true support: - GCC 11 @@ -773,6 +779,7 @@ features: - desc: "`char8_t`: A type for UTF-8 characters and strings" paper: P0482 summary: "A distinct type for UTF-8 encoded characters and strings." + content: char8-t.md support: - GCC 9 - Clang 7 @@ -808,6 +815,7 @@ features: - desc: "Immediate functions (`consteval`)" paper: P1073 summary: "Functions that must be evaluated at compile time." + content: consteval.md support: - GCC 10 (partial) - GCC 11 @@ -1018,6 +1026,7 @@ features: - desc: "The One Ranges Proposal" paper: P0896 + content: ranges.md lib: true support: - GCC 10 @@ -1118,6 +1127,7 @@ features: - desc: "Modules" paper: [P1103, P1703, P1766, P1779, P1811, P1815, P1857, P1874, P1979, P2115, P2615, P2788] summary: "Modern alternative to header files with better encapsulation and faster builds." + content: modules.md support: - GCC 11 (partial) - GCC 15 (hint) @@ -1162,6 +1172,7 @@ features: - desc: "Coroutines" paper: [P0912, LWG3393] summary: "Suspendable functions for async programming and generators." + content: coroutines.md support: - GCC 10 (hint) - Clang 8 (partial) @@ -1310,6 +1321,7 @@ features: - desc: "`using` enum" paper: P1099 summary: "Import enum members into the current scope." + content: using-enum.md support: - GCC 11 - Clang 13 @@ -1349,6 +1361,7 @@ features: - desc: "`constinit`" paper: P1143 summary: "Ensure static/thread-local variables are constant-initialized." + content: constinit.md support: - GCC 10 - Clang 10 @@ -1434,6 +1447,7 @@ features: - desc: "`std::format()`" paper: P0645 + content: format.md lib: true support: - GCC 13 @@ -1585,6 +1599,7 @@ features: - desc: "`constexpr` `std::vector`" paper: P1004 + content: constexpr-vector.md lib: true support: - GCC 12 @@ -1598,6 +1613,7 @@ features: - desc: "`std::stop_token` and `std::jthread`" paper: P0660 + content: jthread.md lib: true support: - GCC 10 @@ -1629,6 +1645,7 @@ features: - desc: "`std::source_location`" paper: P1208 + content: source-location.md lib: true support: - GCC 11