From c6f3ee85a136cdef443b3bd8807b3ae5f157cc47 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 19 Oct 2026 00:52:02 +0700 Subject: [PATCH 1/5] docs: extend functions chapter through C++26 and normalize chapter structure --- 01_low_level/09_functions/README.md | 114 ++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 15 deletions(-) diff --git a/01_low_level/09_functions/README.md b/01_low_level/09_functions/README.md index e88be1a..045e8b3 100644 --- a/01_low_level/09_functions/README.md +++ b/01_low_level/09_functions/README.md @@ -1,4 +1,4 @@ -# 09. Functions and their Evolution in Modern C++ +# 09. Functions from C++98 to C++26: Types, Calls, ABI, and Callable Objects > Functions are the fundamental abstraction boundary in C++. > They combine language rules, ABI conventions, optimizer behavior, and C legacy. @@ -499,7 +499,7 @@ Use `va_list` correctly or avoid entirely. --- -## 7. Attributes (`[[...]]`) +## 6. Attributes (`[[...]]`) Attributes annotate functions for tools and optimizers. @@ -781,7 +781,7 @@ If you want, I can add a follow-up file showing: --- -## 8. Static local variables +## 7. Static local variables ```cpp void f() { @@ -797,7 +797,7 @@ Facts: --- -## 9. Parameter passing and decay +## 8. Parameter passing and decay ### Arrays decay to pointers @@ -812,7 +812,7 @@ void f(int[1024]); // all identical --- -## 10. Variadic interfaces +## 9. Variadic interfaces ### Three approaches @@ -830,7 +830,7 @@ void f(std::initializer_list); --- -## 11. Default arguments +## 10. Default arguments * Only for trailing parameters * Bound at call site @@ -842,7 +842,7 @@ void log(int level = get_default()); --- -## 13. Inline functions +## 11. Inline functions ### Meaning of `inline` @@ -866,7 +866,7 @@ void log(int level = get_default()); * If you put the inline function's definition into a `*.cpp` file, and if it is called from another `*.cpp` file, you'll get an "unresolved external" error from the linker --- -## 14. Overload resolution (deep dive) +## 12. Overload resolution (deep dive) Resolution order: @@ -884,7 +884,7 @@ Resolution order: --- -## 15. Initializer lists `{}` +## 13. Initializer lists `{}` ### Deduction rules @@ -905,7 +905,7 @@ f({}); // error: type unknown --- -## 16. C++ Callables & Lambdas +## 14. C++ Callables & Lambdas This is a detailed reference on "callables" in C++ and the evolution of lambdas: - C++98 function objects ("functors") @@ -1284,7 +1284,7 @@ auto add10 = [](int x){ return add_free(10, x); }; ``` `std::bind` pitfalls: -- placeholder indexing is hard to read +- argument indexing is hard to read - value vs reference capture surprises (use `std::ref`) - error messages can be noisy @@ -1472,7 +1472,7 @@ If you want, I can add: --- -## 18. Macros vs functions +## 15. Macros vs functions ### 1. What the preprocessor is (and is not) @@ -1844,7 +1844,7 @@ Small compile-time toggles can be appropriate, especially in embedded, or when y --- -## 19. At-Exit Functions +## 16. At-Exit Functions * The quick_exit() function is like exit() except that it does not invoke any destructors * Register functions to be invoked by quick_exit() using at_quick_exit() @@ -1854,6 +1854,90 @@ Small compile-time toggles can be appropriate, especially in embedded, or when y * std::quick_exit: doesn't execute static destructors, but does flush critical IO and call handler if exists --- +## Advanced function rules that matter from C++20 to C++26 + +### C++20 immediate functions distinguish evaluation time from return type + +A `consteval` function must be invoked in an immediate-function context or as part of an allowed immediate invocation; its result must meet constant-evaluation rules. `constexpr` does not impose this requirement on every call. Neither keyword automatically makes expensive computations free: compile-time work consumes compiler resources and can shift cost from runtime into builds. + +```cpp +consteval unsigned mask(unsigned bit) { return 1u << bit; } +constexpr unsigned selected = mask(5); +static_assert(selected == 32); +``` + +For untrusted or runtime indices, validate the shift precondition and use a normal runtime function. C++23 relaxed several constexpr function restrictions, and C++26 extends constant-evaluation capabilities further, but language-version support must be tested on the actual compiler. + +### C++23 explicit object parameters replace many cv/ref overloads + +The explicit object parameter, often called *deducing this*, gives a member function an explicit `this`-object parameter. The mechanism can reduce repetitive overloads for lvalue/rvalue and const/non-const access, and its type deduction interacts with forwarding in ways that ordinary implicit object parameter declarations do not. + +```cpp +// C++23, when __cpp_explicit_this_parameter >= 202110L: +struct Counter { + int value = 0; + template + decltype(auto) get(this Self&& self) { + return std::forward(self).value; + } +}; +``` + +In actual builds include `` and guard the declaration itself; a C++20 compiler cannot parse the syntax even if it is inside an uninstantiated template. Explicit-object member functions differ from implicit-object members in pointer-to-member types, so check callbacks and ABI assumptions before modernizing widely exported APIs. + +### C++23 `std::invoke_r` separates invocation from return conversion + +`std::invoke` (C++17) unifies function objects, functions, and member pointers. C++23 `std::invoke_r` additionally requests a conversion of the result to `R`, or discards it for `void`. + +```cpp +#if defined(__cpp_lib_invoke_r) && __cpp_lib_invoke_r >= 202106L +auto f = [] { return 42; }; +long result = std::invoke_r(f); +#endif +``` + +This is preferable to informal `std::function` wrapping when no type erasure or ownership of callable state is needed. It does not turn arbitrary non-invocable expressions into valid invocations. + +### C++23 `std::move_only_function` represents non-copyable callables + +`std::function` historically required a copy-constructible stored target. C++23 adds `std::move_only_function`, which can hold a callable with move-only captures and can encode cv/ref and `noexcept` qualifiers in the wrapper's signature. + +```cpp +#if defined(__cpp_lib_move_only_function) && __cpp_lib_move_only_function >= 202110L +std::move_only_function task = + [p = std::make_unique(7)] { return *p; }; +assert(task() == 7); +#endif +``` + +The wrapper is owning and type-erased; an empty invocation has preconditions. For non-owning callbacks, C++26 `std::function_ref` provides a different lifetime contract. + +### C++26 `std::function_ref` avoids ownership but exposes dangling hazards + +A `std::function_ref` is a lightweight reference to a callable, useful for synchronous algorithms that invoke it but do not retain it. Unlike `std::function` and `std::move_only_function`, it does not own the target or extend its lifetime. + +```cpp +// C++26 with and __cpp_lib_function_ref support: +void for_each_item(std::span data, + std::function_ref callback); +``` + +Do not store such a reference for asynchronous use unless the callable's owner is explicitly guaranteed to survive. The benefit comes from communicating borrowing semantics rather than from promising zero overhead on every ABI. + +### Coroutines are language transformations, not ordinary resumable function calls + +C++20 coroutines use `co_await`, `co_yield`, or `co_return` to transform a function into a state machine whose frame and lifetime follow coroutine rules. The return type must supply an appropriate promise type. The compiler-generated frame can outlive the initial call, which changes what captured references and parameters are safe. + +A coroutine that retains a reference to a stack local across suspension can dangle even when the same code would be safe in a synchronous function. Carefully distinguish parameters copied into the coroutine frame from objects merely referenced by it. Consider cancellation, exception propagation, and destruction of suspended frames as part of the function's contract. + +### C++26 pack indexing refines generic function parameter packs + +C++26 pack indexing (`Ts...[I]` in type contexts and `pack...[I]` in expression contexts) makes it possible to select a specific type or argument from a pack without recursive helper machinery. It does not alter the evaluation, forwarding, or lifetime rules of the selected argument. + +Use a feature test such as `__cpp_pack_indexing` before introducing these expressions. Template design should still prefer a clear concept/constraint and describe what happens when the selected pack index is out of bounds. + +--- + ## Rules worth keeping in working memory The central distinction in functions and callable interfaces is between what the C++ type system guarantees, what the implementation commonly does, and what an API merely assumes. Carrying that distinction from declarations through operations avoids most of the surprises discussed above. @@ -2005,9 +2089,9 @@ Concepts, `consteval`, coroutines, and richer constexpr rules extend callable co Explicit object parameters and `if consteval` improve member and immediate-function APIs. -### C++26: Emerging improvements +### C++26: Borrowed callables and pack indexing -New callable and constexpr facilities are implementation-dependent until shipped. +C++26 standardizes `std::function_ref` as a non-owning callable view and pack indexing for directly selecting elements of parameter packs. Neither change removes callable lifetime or forwarding obligations; library support is feature-dependent. When documenting a facility, state its required language/library version rather than inferring support from the compiler's branding. From 4277811d6139c6d682e16fb2610b7e6b7e398111 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 19 Oct 2026 00:58:05 +0700 Subject: [PATCH 2/5] test: add reusable Python chapter structure checker --- scripts/check_chapter_structure.py | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 scripts/check_chapter_structure.py diff --git a/scripts/check_chapter_structure.py b/scripts/check_chapter_structure.py new file mode 100644 index 0000000..bb89f76 --- /dev/null +++ b/scripts/check_chapter_structure.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Validate Book 01 technical-literature chapter contracts (stdlib only).""" +from __future__ import annotations + +import argparse +from pathlib import Path +import re +import sys + +ENDING = ( + "Rules worth keeping in working memory", + "Diagnostics, useful compiler settings and extensions", + "Standards timeline", + "Migration note for Java / Python / C# developers", + "Further reading", +) +YEARS = ("C++98/03", "C++11", "C++14", "C++17", "C++20", "C++23", "C++26") +STUBS = re.compile(r"\b(?:TODO|TBD|FIXME|PLACEHOLDER|fill this in)\b|", re.I) +HEADING = re.compile(r"^(#{1,6})\s+(.+?)\s*$", re.M) +FENCE = re.compile(r"^\s*(`{3,}|~{3,})", re.M) +CHAPTER = re.compile(r"^(\d{2})_") + +def validate(path: Path) -> list[str]: + body = path.read_text(encoding="utf-8") + errors: list[str] = [] + chapter = CHAPTER.match(path.parent.name) + if not chapter: + return [f"{path}: not a numbered chapter directory"] + headings = [(len(m.group(1)), m.group(2)) for m in HEADING.finditer(body)] + if not headings or headings[0][0] != 1 or not headings[0][1].startswith(chapter.group(1)): + errors.append("H1 must start with chapter number") + h2 = [h for level, h in headings if level == 2] + if h2[-len(ENDING):] != list(ENDING): + errors.append("final five H2 sections must match the chapter template in order") + for h in ENDING: + if h2.count(h) != 1: + errors.append(f"expected exactly one H2: {h}") + numeric = [int(m.group(1)) for h in h2 if (m := re.match(r"^(\d+)\.\s", h))] + if not numeric or numeric != list(range(1, len(numeric) + 1)): + errors.append(f"numbered H2 sections not consecutive: {numeric}") + found_stubs = STUBS.findall(body) + if found_stubs: + errors.append(f"contains stub markers: {found_stubs[:3]}") + fences = FENCE.findall(body) + if len(fences) % 2: + errors.append("unbalanced fenced code blocks") + timeline_start = body.find("\n## Standards timeline") + migration_start = body.find("\n## Migration note") + if timeline_start >= 0 and migration_start > timeline_start: + timeline = body[timeline_start:migration_start] + missing = [std for std in YEARS if not re.search(r"^### " + re.escape(std) + r"\b", timeline, re.M)] + if missing: + errors.append(f"standards timeline missing: {', '.join(missing)}") + if len(body) < 7000: + errors.append("chapter is unexpectedly short for advanced technical literature") + for item in errors: + print(f"{path}: {item}", file=sys.stderr) + if not errors: + print(f"PASS {path} ({len(numeric)} numbered sections)") + return errors + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("paths", nargs="*", type=Path, + help="README.md paths; defaults to chapters 01–13") + args = ap.parse_args() + paths = args.paths or sorted(Path("01_low_level").glob("[0-9][0-9]_*/README.md")) + if not paths: + print("No chapter READMEs found", file=sys.stderr) + return 2 + failures = sum(bool(validate(p)) for p in paths) + return 1 if failures else 0 + +if __name__ == "__main__": + raise SystemExit(main()) From fa6277b937ed95b1181f6fa71f1a98ac2439a9a9 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 19 Oct 2026 00:58:08 +0700 Subject: [PATCH 3/5] ci: check modified chapter markdown --- .github/workflows/chapter-structure.yml | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/chapter-structure.yml diff --git a/.github/workflows/chapter-structure.yml b/.github/workflows/chapter-structure.yml new file mode 100644 index 0000000..f0b0042 --- /dev/null +++ b/.github/workflows/chapter-structure.yml @@ -0,0 +1,37 @@ +name: Chapter Markdown Structure +on: + pull_request: + paths: + - '01_low_level/**/README.md' + - 'scripts/check_chapter_structure.py' + - '.github/workflows/chapter-structure.yml' + push: + branches: [master] + paths: + - '01_low_level/**/README.md' + - 'scripts/check_chapter_structure.py' + - '.github/workflows/chapter-structure.yml' + workflow_dispatch: +jobs: + chapter-contract: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Validate Book 01 chapter structure + shell: bash + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + mapfile -t files < <(git diff --name-only --diff-filter=ACMR "${{ github.event.pull_request.base.sha }}" HEAD -- '01_low_level/*/README.md') + if (( ${#files[@]} )); then + python scripts/check_chapter_structure.py "${files[@]}" + else + echo "No changed chapter READMEs to validate" + fi + else + python scripts/check_chapter_structure.py + fi From 7163f78a3cdf0d9a8f6054b484216e36817a4568 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 19 Oct 2026 01:03:45 +0700 Subject: [PATCH 4/5] ci: validate only changed chapters on push and PR; keep manual full-book audit --- .github/workflows/chapter-structure.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/chapter-structure.yml b/.github/workflows/chapter-structure.yml index f0b0042..f2afa8e 100644 --- a/.github/workflows/chapter-structure.yml +++ b/.github/workflows/chapter-structure.yml @@ -26,12 +26,16 @@ jobs: shell: bash run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then - mapfile -t files < <(git diff --name-only --diff-filter=ACMR "${{ github.event.pull_request.base.sha }}" HEAD -- '01_low_level/*/README.md') - if (( ${#files[@]} )); then - python scripts/check_chapter_structure.py "${files[@]}" - else - echo "No changed chapter READMEs to validate" - fi + base="${{ github.event.pull_request.base.sha }}" + elif [[ "${{ github.event_name }}" == "push" ]]; then + base="${{ github.event.before }}" else python scripts/check_chapter_structure.py + exit + fi + mapfile -t files < <(git diff --name-only --diff-filter=ACMR "$base" HEAD -- '01_low_level/*/README.md') + if (( ${#files[@]} )); then + python scripts/check_chapter_structure.py "${files[@]}" + else + echo "No changed chapter READMEs to validate" fi From 9cc76f3e10c03c5642da2a1ef39f3f954e8919f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 14:09:13 +0000 Subject: [PATCH 5/5] Fix chapter 09 review comments Co-authored-by: yuchdev <678436+yuchdev@users.noreply.github.com> --- 01_low_level/09_functions/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/01_low_level/09_functions/README.md b/01_low_level/09_functions/README.md index 045e8b3..cf5a9b9 100644 --- a/01_low_level/09_functions/README.md +++ b/01_low_level/09_functions/README.md @@ -1854,7 +1854,7 @@ Small compile-time toggles can be appropriate, especially in embedded, or when y * std::quick_exit: doesn't execute static destructors, but does flush critical IO and call handler if exists --- -## Advanced function rules that matter from C++20 to C++26 +## 17. Advanced function rules that matter from C++20 to C++26 ### C++20 immediate functions distinguish evaluation time from return type @@ -1878,7 +1878,7 @@ struct Counter { int value = 0; template decltype(auto) get(this Self&& self) { - return std::forward(self).value; + return (std::forward(self).value); } }; ```