Skip to content
Open
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
41 changes: 41 additions & 0 deletions .github/workflows/chapter-structure.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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
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
113 changes: 99 additions & 14 deletions 01_low_level/11_namespaces/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ They exist to structure programs, prevent naming conflicts, and enable large-sca

---

## Purpose of namespaces
## 1. Purpose of namespaces

* Use namespaces to express logical structure (C++ Standard 14.3.1)
* Prevent name collisions across large codebases and libraries
Expand All @@ -27,7 +27,7 @@ Namespaces **do not**:

---

## Linkage and namespaces
## 2. Linkage and namespaces

* A name that can be used in translation units different from the one in which it was defined is said to have **external linkage**
* A name that can be referred to only in the translation unit in which it is defined is said to have **internal linkage**
Expand All @@ -50,7 +50,7 @@ extern const int y = 20; // explicitly external

---

## Unnamed (anonymous) namespaces
## 3. Unnamed (anonymous) namespaces

* An unnamed namespace can be used to make names local to a compilation unit
* The effect of an unnamed namespace is very similar to that of internal linkage
Expand All @@ -64,14 +64,14 @@ namespace {

### Important details

* Entities technically have external linkage
* But they are **unreachable** from other translation units
* Unnamed-namespace members have internal linkage in the usual C++ model.
* Their names are local to the translation unit
* Preferred modern replacement for `static` globals
* Applies to variables, functions, classes, templates

---

## Named namespaces and namespace merging
## 4. Named namespaces and namespace merging

Namespaces can be **defined in multiple places** and are merged by name:

Expand All @@ -93,7 +93,7 @@ namespace my {

---

## Namespace aliases
## 5. Namespace aliases

Namespace aliases provide shorter or version-neutral access:

Expand All @@ -114,7 +114,7 @@ namespace lib = very_long_vendor_library;

---

## `using` declarations and `using` directives
## 6. `using` declarations and `using` directives

### `using` directive

Expand All @@ -141,7 +141,7 @@ using std::vector;

---

## Argument-Dependent Lookup (ADL)
## 7. Argument-Dependent Lookup (ADL)

Namespaces participate in **argument-dependent lookup** (Koenig lookup):

Expand Down Expand Up @@ -169,7 +169,7 @@ normalize(v); // found via ADL

---

## Namespaces and operator overloading
## 8. Namespaces and operator overloading

Operators should usually be **free functions in the same namespace as their operands**:

Expand All @@ -187,7 +187,7 @@ namespace math {

---

## Inline namespaces (C++11+)
## 9. Inline namespaces (C++11+)

* The `inline` specifier makes a nested namespace the **default meaning** of the enclosing namespace
* This is primarily used for **API and ABI versioning**
Expand Down Expand Up @@ -230,7 +230,7 @@ using lib;

---

## Headers, includes, and namespaces
## 10. Headers, includes, and namespaces

* Spaces are significant within the `< >` or `" "` of an include directive
* The absence of a `.h` suffix does **not** imply anything about how the header is stored
Expand All @@ -245,7 +245,7 @@ using lib;

---

## C linkage and namespaces
## 11. C linkage and namespaces

* We can specify a linkage convention to be used in an `extern` declaration
* `extern "C"` specifies C linkage (no name mangling)
Expand Down Expand Up @@ -274,6 +274,91 @@ extern "C" {

---

## 12. Modern name lookup and namespace evolution: advanced boundaries

### ADL uses associated classes and namespaces, not a textual global search

Argument-dependent lookup adds candidate functions from namespaces and classes associated with the argument types. It enables `swap(a,b)` customization and the hidden-friend idiom but can introduce overload candidates that ordinary unqualified lookup would not find.

```cpp
namespace network {
struct Packet {
friend bool operator==(const Packet&, const Packet&) = default;
};
}
bool same(network::Packet a, network::Packet b) { return a == b; }
```

The friend function can be found through argument-dependent lookup even though it is not ordinarily visible through namespace-qualified lookup. This is why adding operators or converting constructors can change overload resolution across otherwise unrelated call sites.

### Hidden friends constrain lookup surface but are not private implementation secrets

A friend function first declared within a class is a namespace-scope function associated with that class, and often participates in ADL without being found by ordinary unqualified lookup. This pattern improves generic operator design and prevents inappropriate implicit conversions from making overload candidates available too broadly.

Do not confuse it with member-function access control or module export restrictions. A hidden friend can still be part of a public library API, and changing its signature can create source or binary compatibility issues.

### C++17 nested-namespace definitions reduce boilerplate only

`namespace library::detail { ... }` is shorthand for nested namespace blocks. It does not create a visibility boundary, a module partition, or an implicitly private implementation area.

```cpp
namespace library::detail {
inline constexpr int format_version = 2;
}
```

If an implementation name is in a public header, client code may depend on it despite the convention `detail`. A versioned public API requires documentation and change discipline rather than relying on namespace spelling to hide entities.

### C++20 modules decouple namespaces from compilation units

Named module boundaries govern reachability and module linkage; namespaces remain a name-organization and lookup facility. Two modules can define names in the same namespace without becoming the same module, and an exported namespace-qualified declaration must still comply with the relevant module interface rules.

```cpp
// Interface of a C++20 named module (illustrative)
export module example.math;
export namespace example {
int calculate(int);
}
```

A module name is not a namespace, and `import example.math;` is not a `using namespace example;` directive. This distinction is essential in migrations from textual headers, particularly where dependent lookup and template instantiation are involved.

### C++20 `using enum` may reintroduce exactly the collisions scoped enums prevented

Scoped enum members are normally qualified names. `using enum Color;` introduces them into the local scope, which is convenient for switches but can conflict with other local declarations or imported enumerators.

Prefer it within a small function scope with a clear enum domain. Avoid putting a blanket `using enum` into a public header, just as you would avoid a broad namespace using-directive there.

### Inline namespaces help version APIs but complicate overload sets

An inline namespace makes its members available for lookup as though injected into the enclosing namespace while retaining an explicit qualified name. This supports coexistence of ABI versions but does not by itself prevent conflicting overloads, duplicate definitions, or symbol-export errors.

```cpp
namespace api {
inline namespace v2 { int parse(const char*); }
namespace v1 { int parse(const char*); }
}
```

Be cautious when changing which namespace is inline: ADL, mangled names, template specializations, and client rebuild requirements may change. ABI versioning needs toolchain-specific symbol verification.

### Namespace aliases are not ABI aliases

A namespace alias redirects source-level name lookup; it does not rename definitions or exported symbols, and cannot be reopened as a namespace definition.

```cpp
namespace vendor::version_2 { void dispatch(); }
namespace client_api = vendor::version_2;
```

Use aliases to reduce qualification noise, not as proof that a linked library can replace one namespace's ABI with another.

### C++23/26 reflection does not change foundational namespace lookup

Recent language advances such as explicit object parameters and C++26 static reflection introduce new forms of compile-time inspection and declarations, but do not make namespaces runtime objects or make ADL optional. Generic code still needs explicit reasoning about ordinary lookup, dependent lookup, argument-associated candidates, and two-phase template lookup.

---

## Rules worth keeping in working memory

The controlling distinction for namespace lookup is between the portable language contract and the behavior of a particular compilation environment. C++ gives programmers the ability to build close to system boundaries, but also requires those boundaries to be specified rather than guessed.
Expand Down Expand Up @@ -411,7 +496,7 @@ Modules affect declaration visibility but do not make namespaces modules.

No material change to namespace lookup fundamentals.

### C++26: Forthcoming support
### C++26: Evolving tools without fundamental linkage or lookup changes

New reflection/library features still obey namespace and lookup rules.

Expand Down
75 changes: 75 additions & 0 deletions scripts/check_chapter_structure.py
Original file line number Diff line number Diff line change
@@ -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|<placeholder>", 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())
Loading