07 Multi-Language Projects: C++ Code Quality and Testing
January 2026 (8892 Words, 50 Minutes)
1. Introduction: Building on C++ Fundamentals
In the previous lecture (Part 1), you learned the fundamentals of C++ development:
- How C++ compiles to native machine code (bypassing the Python Virtual Machine)
- The build process: preprocessing, compilation, linking
- CMake as the cross-platform build configuration tool
- Dependency management with
find_package()andFetchContent - Header/source file organization
This lecture focuses on ensuring code quality:
Writing code that compiles is only half the battle—we need code that’s readable, maintainable, and well-tested.
┌─────────────────────────────────────────────────────────────────┐
│ C++ QUALITY PIPELINE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Source Code → clang-format → clang-tidy → Google Test → gcov │
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ │
│ Your .cpp Consistent Bug-free Verified Measured │
│ files style code behavior coverage │
│ │
│ Just like Python: Ruff → Pyright → pytest → pytest-cov │
│ │
└─────────────────────────────────────────────────────────────────┘
This lecture covers the C++ equivalents to your Python quality tools:
- Code formatting — clang-format (like Black/Ruff)
- Static analysis — clang-tidy + compiler warnings (like Pyright/Ruff)
- Unit testing — Google Test (like pytest)
- Coverage reports — gcov/llvm-cov (like pytest-cov)
By the end, you’ll have the same quality assurance for C++ that you already have for Python. Part 3 will then show you how to integrate both languages in a single project.
2. Learning Objectives
By the end of this lecture, you will be able to:
Code Formatting:
- Apply clang-format to enforce consistent C++ code style
- Configure
.clang-formatfiles using the Google C++ Style Guide
Static Analysis:
- Configure compiler warnings across GCC, Clang, and MSVC
- Use clang-tidy for advanced static analysis and modernization checks
Unit Testing:
- Write Google Test tests with
TEST(),EXPECT_*, andASSERT_*macros - Use test fixtures for shared setup and teardown
Coverage:
- Generate C++ coverage reports using gcov/llvm-cov
- Interpret coverage metrics and identify untested code paths
Comparison:
- Map Python quality tools to C++ equivalents (Ruff → clang-format/clang-tidy, pytest → Google Test, pytest-cov → gcov)
3. Code Quality for C++
In Part 1, we learned how to compile C++ code, use CMake for build configuration, and manage dependencies. But writing code that compiles is only half the battle—we also need code that’s readable, maintainable, and bug-free.
You already know this discipline from Python. Remember the road-profile-viewer’s pyproject.toml?
[tool.ruff]
line-length = 120
target-version = "py312"
[tool.ruff.lint]
select = ["E", "W", "F", "I", "N", "B", "C4", "UP"]
[tool.pyright]
typeCheckingMode = "strict"
pythonVersion = "3.12"
This configuration enforces:
- Consistent formatting (line length, indentation)
- Linting rules (error codes like E, W, F)
- Type checking (Pyright in strict mode)
Now we need the same discipline for C++. But there’s a complication…
3.1 The Problem: C++ Has No PEP 8
In Python, code quality is remarkably standardized:
┌─────────────────────────────────────────────────────────────────────┐
│ PYTHON CODE QUALITY ECOSYSTEM │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Style Guide: PEP 8 (the standard, everyone agrees) │
│ │ │
│ ▼ │
│ Tool: Ruff (or Black + Flake8 + isort) │
│ │ │
│ ▼ │
│ Configuration: pyproject.toml (one file for everything) │
│ │ │
│ ▼ │
│ Result: Consistent code across all Python projects │
│ │
└─────────────────────────────────────────────────────────────────────┘
C++ is more fragmented:
┌─────────────────────────────────────────────────────────────────────┐
│ C++ CODE QUALITY ECOSYSTEM │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Style Guides: Google Style LLVM Style Mozilla Style │
│ GNU Style Chromium Linux Kernel │
│ MISRA C++ (and dozens more...) │
│ │ │
│ ▼ │
│ Tools: clang-format (formatting) │
│ clang-tidy (linting) │
│ cppcheck, cpplint, PVS-Studio, ... │
│ │ │
│ ▼ │
│ Configuration: .clang-format + .clang-tidy (separate files) │
│ No single standard configuration │
│ │ │
│ ▼ │
│ Result: Every project looks different │
│ │
└─────────────────────────────────────────────────────────────────────┘
Why this matters:
When you clone a new Python project, you can immediately read the code—it all looks like PEP 8. When you clone a C++ project, you might find:
- 2-space indentation (Google) or 4-space (GNU) or tabs (Linux kernel)
- Braces on same line or new line
camelCaseorsnake_caseorPascalCasefor functions- Different include ordering conventions
The good news: The C++ community has converged on two excellent tools from the LLVM project:
- clang-format — Automatic code formatting (like
ruff format) - clang-tidy — Static analysis and linting (like
ruff check)
With proper configuration, you can achieve the same consistency in C++ that you’re used to in Python.
3.2 The Google C++ Style Guide
For this course, we’ll use the Google C++ Style Guide—one of the most widely adopted standards. Google open-sourced their internal style guide, and it’s now used by:
- Google (obviously)
- Many Chromium-based browsers (Chrome, Edge, Brave)
- TensorFlow and other Google ML projects
- Many open-source projects that want a well-documented standard
Why Google Style for this course?
| Reason | Explanation |
|---|---|
| Well-documented | The style guide explains why each rule exists, not just what to do |
| Tool support | clang-format has built-in BasedOnStyle: Google |
| Python-friendly | Uses snake_case for functions/variables (like Python!) |
| Modern C++ | Updated for C++11/14/17/20 features |
| Industry adoption | Knowing Google Style is useful for your career |
3.3 Naming Conventions: Python vs. Google C++
Here’s how naming conventions compare:
| Element | Python (PEP 8) | Google C++ | Example |
|---|---|---|---|
| Functions | snake_case |
snake_case |
calculate_distance() |
| Variables | snake_case |
snake_case |
ray_angle |
| Classes | PascalCase |
PascalCase |
RoadProfile |
| Constants | ALL_CAPS |
kPascalCase |
Python: MAX_SPEEDC++: kMaxSpeed |
| Private members | _leading_underscore |
trailing_underscore_ |
Python: self._dataC++: data_ |
Notice: Functions and variables use the same convention! This is intentional—Google’s style guide was influenced by Python.
// Google C++ Style - feels familiar to Python developers
class RoadProfile {
public:
double calculate_elevation(double x_position) const;
private:
std::vector<double> elevation_data_; // trailing underscore for private
};
// compare to Python:
// class RoadProfile:
// def calculate_elevation(self, x_position: float) -> float:
//
// _elevation_data: list[float] # leading underscore for private
3.4 Formatting Conventions
Some key differences from Python:
// Google C++ Style
// 2-space indentation (not 4 like Python)
if (condition) {
do_something();
do_something_else();
}
// Opening brace on same line (like Python's ":" at end of line)
void calculate_ray_line(double angle) {
// implementation
}
// Line length: 80 characters (configurable to 100/120)
// This matches what you configured in pyproject.toml: line-length = 120
The philosophy is the same: Consistent formatting makes code easier to read. The specific rules differ slightly, but the discipline is identical.
4. clang-format: The C++ Formatter
In Python, you run ruff format (or black) to automatically format your code. In C++, the equivalent is clang-format.
4.1 How clang-format Relates to Ruff
Think of clang-format as “Ruff format for C++”:
| Aspect | Python (Ruff format) | C++ (clang-format) |
|---|---|---|
| Purpose | Auto-format Python code to PEP 8 | Auto-format C++ code to chosen style |
| Config file | pyproject.toml or ruff.toml |
.clang-format |
| Format in place | ruff format . |
clang-format -i file.cpp |
| Check only (CI) | ruff format --check . |
clang-format --dry-run --Werror file.cpp |
| Base style | PEP 8 (implicit) | BasedOnStyle: Google (explicit) |
4.2 Configuration Comparison: pyproject.toml vs .clang-format
Let’s compare the formatting configuration from your road-profile-viewer with an equivalent C++ configuration:
Python (pyproject.toml):
[tool.ruff]
line-length = 120
target-version = "py312"
[tool.ruff.format]
quote-style = "double"
line-ending = "lf"
indent-style = "space"
docstring-code-format = true
C++ (.clang-format):
# .clang-format
---
Language: Cpp
BasedOnStyle: Google
# Line length (like line-length = 120)
ColumnLimit: 100
# Indentation (like indent-style = "space")
IndentWidth: 2
UseTab: Never
# Quote style doesn't apply (C++ uses "" for strings, '' for chars)
# But we can control include style:
IncludeBlocks: Regroup
# Brace style
BreakBeforeBraces: Attach
# Short forms
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
# Template formatting
AlwaysBreakTemplateDeclarations: Yes
# Include ordering (like isort in Python)
IncludeCategories:
- Regex: '^<.*\.h>' # C headers first
Priority: 1
- Regex: '^<.*>' # C++ standard library
Priority: 2
- Regex: '^".*"' # Project headers last
Priority: 3
...
Key insight: Both configurations solve the same problems—they just use different syntax. The IncludeCategories section in clang-format is directly analogous to what isort (now part of Ruff) does for Python imports.
4.3 Installing clang-format
# Ubuntu/Debian
sudo apt install clang-format
# macOS
brew install clang-format
# Windows (with LLVM - includes clang-format and clang-tidy)
choco install llvm
# Or with winget:
winget install LLVM.LLVM
Verify installation:
clang-format --version
# Output: clang-format version 17.0.6 (or similar)
4.4 Running clang-format
The workflow mirrors what you do with Ruff:
Format a single file (like ruff format file.py):
clang-format -i src/geometry.cpp
The -i flag means “in-place” (modify the file). Without it, clang-format prints the formatted code to stdout.
Check without modifying (for CI, like ruff format --check):
clang-format --dry-run --Werror src/geometry.cpp
--dry-run: Don’t modify the file--Werror: Return non-zero exit code if formatting changes needed
Format all C++ files (like ruff format .):
# Linux/macOS
find . -name '*.cpp' -o -name '*.hpp' | xargs clang-format -i
# Or use a more portable approach with CMake (we'll see this in CI)
4.5 Before and After: clang-format in Action
Before formatting:
#include "geometry.hpp"
#include <cmath>
#include <vector>
#include <iostream>
double calculate_ray_line(double angle,double camera_x,double camera_y,double x_max){
double angle_rad=-angle*3.14159265359/180.0;
double slope=std::tan(angle_rad);
if(std::abs(std::cos(angle_rad))<1e-10){return 0.0;}
double x_end=x_max;
return slope*(x_end-camera_x)+camera_y;
}
After clang-format -i with Google style:
#include <cmath>
#include <iostream>
#include <vector>
#include "geometry.hpp"
double calculate_ray_line(double angle, double camera_x, double camera_y,
double x_max) {
double angle_rad = -angle * 3.14159265359 / 180.0;
double slope = std::tan(angle_rad);
if (std::abs(std::cos(angle_rad)) < 1e-10) {
return 0.0;
}
double x_end = x_max;
return slope * (x_end - camera_x) + camera_y;
}
What changed:
- Includes reordered (standard library first, project headers last)
- Consistent spacing around operators
- Opening brace on same line
- Proper indentation (2 spaces)
- Long parameter list wrapped properly
5. clang-tidy: The C++ Linter
While clang-format handles how your code looks, clang-tidy analyzes what your code does. It catches bugs, suggests improvements, and enforces best practices—just like ruff check does for Python.
5.1 How clang-tidy Relates to Ruff Check
Remember the Ruff configuration in your road-profile-viewer?
[tool.ruff.lint]
select = ["E", "W", "F", "I", "N", "B", "C4", "UP"]
Each letter is a category of checks:
- E: pycodestyle errors
- W: pycodestyle warnings
- F: Pyflakes (likely bugs)
- I: isort (import ordering)
- N: pep8-naming
- B: flake8-bugbear (likely bugs and design issues)
- C4: flake8-comprehensions (simplify comprehensions)
- UP: pyupgrade (modern Python syntax)
clang-tidy works the same way—it has categories of checks you enable or disable:
| Python (Ruff) | C++ (clang-tidy) | What It Catches |
|---|---|---|
F (Pyflakes) |
bugprone-* |
Likely bugs, undefined behavior |
B (Bugbear) |
bugprone-* |
Subtle bugs, dangerous patterns |
C4 (Comprehensions) |
performance-* |
Performance improvements |
UP (pyupgrade) |
modernize-* |
Use modern language features |
N (pep8-naming) |
readability-identifier-naming |
Naming conventions |
E, W (pycodestyle) |
readability-* |
Style and clarity |
| (No direct equivalent) | cppcoreguidelines-* |
C++ Core Guidelines (safety) |
5.2 Configuration Comparison: pyproject.toml vs .clang-tidy
Python (pyproject.toml):
[tool.ruff.lint]
# Select which rule categories to enable
select = ["E", "W", "F", "I", "N", "B", "C4", "UP"]
# Make all rules auto-fixable
fixable = ["ALL"]
# Ignore specific rules
ignore = ["E501"] # Ignore line length (handled by formatter)
C++ (.clang-tidy):
# .clang-tidy
---
# Enable check categories (like select = [...])
Checks: >
-*,
bugprone-*,
performance-*,
modernize-*,
readability-*,
-modernize-use-trailing-return-type,
-readability-magic-numbers
# Treat specific warnings as errors (like --error-on in Ruff)
WarningsAsErrors: ''
# Configure specific checks (like per-rule settings in Ruff)
CheckOptions:
- key: readability-identifier-naming.NamespaceCase
value: lower_case
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: lower_case
- key: readability-identifier-naming.VariableCase
value: lower_case
...
The pattern is identical:
- Start by disabling all checks (
-*or start with empty selection) - Enable categories you want (
bugprone-*or"F") - Disable specific annoying rules (
-readability-magic-numbersorignore = ["E501"])
5.3 Why clang-tidy Needs compile_commands.json
Here’s an important difference: clang-tidy needs to understand your project structure.
Python (Ruff): Can analyze any .py file immediately—Python’s import system is standardized.
C++ (clang-tidy): Needs to know:
- Where are header files located? (
-I./include) - What compiler flags are used? (
-std=c++20) - What macros are defined? (
-DDEBUG)
CMake generates a compile_commands.json file that contains all this information:
# Generate compile_commands.json
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# This creates build/compile_commands.json:
# [
# {
# "directory": "/path/to/project/build",
# "command": "g++ -std=c++20 -I../include -c ../src/geometry.cpp",
# "file": "../src/geometry.cpp"
# },
# ...
# ]
Now clang-tidy knows exactly how each file should be compiled, and can analyze it correctly.
5.4 Running clang-tidy
Step 1: Generate compilation database:
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
Step 2: Run on a single file (like ruff check file.py):
clang-tidy -p build src/geometry.cpp
The -p build flag tells clang-tidy where to find compile_commands.json.
Step 3: Run on all source files (like ruff check .):
# Linux/macOS
find src -name '*.cpp' | xargs clang-tidy -p build
# Or use run-clang-tidy (parallel, faster)
run-clang-tidy -p build src/
5.5 Example: What clang-tidy Catches
Your code:
void process_data(std::vector<double> data) { // Pass by value (expensive copy!)
for (int i = 0; i < data.size(); i++) { // Signed/unsigned comparison
if (data[i] = 0.0) { // Assignment instead of comparison!
continue;
}
}
}
clang-tidy output:
geometry.cpp:1:25: warning: parameter 'data' is passed by value; consider
passing by const reference [performance-unnecessary-value-param]
geometry.cpp:2:21: warning: comparison of integers of different signs
[bugprone-signed-unsigned-comparison]
geometry.cpp:3:21: warning: using the result of an assignment as a condition
without parentheses [bugprone-assignment-in-if-condition]
Compare to Ruff catching similar issues in Python:
def process_data(data):
for i in range(len(data)): # Ruff: Use enumerate() instead [C416]
if data[i] = 0.0: # Python: SyntaxError (caught by interpreter)
continue
The philosophy is the same: catch bugs before they reach production.
5.6 VS Code Integration: clangd Extension
In Python, you installed the Ruff extension for VS Code to get real-time formatting and linting. For C++, the equivalent is clangd—a language server that brings the power of clang-format and clang-tidy directly into your IDE.
Installing clangd:
- Open VS Code Extensions (Ctrl+Shift+X)
- Search for “clangd” (by LLVM)
- Click Install
What clangd provides:
| Feature | Python (Ruff) | C++ (clangd) |
|---|---|---|
| Format on save | Automatic |
Automatic |
| Real-time linting | Squiggly lines |
Squiggly lines |
| Quick fixes | Code actions |
Code actions |
| Go to definition | Pylance/Pyright |
clangd |
| Hover documentation | Type hints |
Type info + docs |
VS Code settings comparison:
Python (settings.json):
{
// Ruff as the formatter
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true
},
// Enable Ruff linting
"ruff.lint.enable": true
}
C++ (settings.json):
{
// clangd as the formatter (uses .clang-format)
"[cpp]": {
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd",
"editor.formatOnSave": true
},
// clangd settings
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--header-insertion=never"
]
}
Key clangd arguments:
| Argument | Purpose | Ruff Equivalent |
|---|---|---|
--clang-tidy |
Enable real-time linting | ruff.lint.enable: true |
--background-index |
Index project for fast navigation | (Pylance does this automatically) |
--header-insertion=never |
Don't auto-add includes | (N/A for Python) |
--compile-commands-dir=build |
Where to find compile_commands.json | (Python doesn't need this) |
The workflow is identical:
- Write code → clangd shows real-time warnings (yellow/red squiggles)
- Save file → clang-format automatically formats
- Hover over warning → See explanation and suggested fix
- Click lightbulb → Apply automatic fix
Recommended extensions for C++ development:
| Extension | Purpose | Python Equivalent |
|---|---|---|
| clangd (LLVM) | Formatting, linting, navigation | Ruff + Pylance |
| CMake Tools | Build, debug, configure CMake | (uv/pip are simpler) |
| C/C++ Extension Pack | Debugger, additional tools | Python Debugger |
Important: If you have the Microsoft C/C++ extension installed, you may need to disable its IntelliSense to avoid conflicts with clangd. Add to settings.json:
{
"C_Cpp.intelliSenseEngine": "disabled"
}
The key insight: Just as you configured Ruff in VS Code to format and lint Python on save, clangd does the same for C++. The configuration files are different (.clang-format + .clang-tidy vs pyproject.toml), but the developer experience is identical.
6. Static Code Analysis
Static analysis examines code without running it. In Python, this was limited because Python is dynamically typed. In C++, static analysis is more powerful because of static typing.
6.1 Why Static Code Analysis?
Before diving into tools, let’s understand why static analysis matters:
1. Catch Bugs Before Runtime
Static analysis finds issues that would otherwise only appear at runtime (or worse, in production):
| Issue Type | Without Static Analysis | With Static Analysis |
|---|---|---|
| Null pointer dereference | Crash in production | Caught at compile time |
| Buffer overflow | Security vulnerability | Warning before merge |
| Resource leak | Memory grows over time | Detected in PR review |
| Type confusion | Undefined behavior | Compiler error |
2. Reduce Code Review Burden
When static analysis catches style issues and obvious bugs automatically, human reviewers can focus on:
- Architecture decisions
- Algorithm correctness
- Business logic
- Edge cases
3. Enforce Consistency Across Team
Without automated checks, code style depends on who reviews the PR. With static analysis:
- Every PR is checked the same way
- New team members get immediate feedback
- Style debates become “let the tool decide”
4. Cost of Fixing Bugs Increases Over Time
Cost to fix a bug:
During coding: 1x (you're already there)
During code review: 5x (context switch for reviewer)
During testing: 10x (write test, debug, fix)
In production: 100x (incident response, customer impact)
Static analysis catches bugs at the 1x cost stage.
6.2 CI Integration: The Key to Effective Analysis
Having static analysis tools installed locally is useful, but the real power comes from integrating them into your CI pipeline. The key principle:
Every merged PR must pass static analysis. No exceptions.
Why CI Integration is Essential:
- Developers can’t skip it - Local checks can be bypassed; CI cannot
- Consistent environment - Same compiler version, same flags, same tools
- Visible to everyone - PR status shows pass/fail for all reviewers
- Historical tracking - See quality trends over time
Python CI Example (from Lecture 2):
# .github/workflows/ci.yml
name: Python Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Run Ruff linter
run: uv run ruff check src/
- name: Run Ruff formatter check
run: uv run ruff format --check src/
- name: Run Pyright type checker
run: uv run pyright src/
C++ CI Example:
# .github/workflows/cpp-ci.yml
name: C++ Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure CMake
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
- name: Build with warnings as errors
run: cmake --build build -- -j$(nproc)
# Build fails if any warning occurs (-Werror)
- name: Run clang-tidy
run: |
find src -name '*.cpp' | xargs clang-tidy \
-p build \
--warnings-as-errors='*'
- name: Check formatting
run: |
find src -name '*.cpp' -o -name '*.hpp' | xargs clang-format --dry-run --Werror
The PR Gate Pattern:
Developer pushes code
↓
CI runs automatically
↓
┌───────────────────────────────────┐
│ ✅ Build passes │
│ ✅ clang-format check passes │
│ ✅ clang-tidy check passes │
│ ✅ All tests pass │
└───────────────────────────────────┘
↓
PR can be merged (green checkmark)
If any check fails, the PR is blocked until fixed.
6.3 Monitoring and Dashboards
Running checks in CI is step one. Step two is making results visible and actionable.
GitHub Actions: Inline Annotations
GitHub Actions can show warnings directly in the PR diff:
- name: Run clang-tidy with annotations
run: |
clang-tidy src/*.cpp 2>&1 | \
sed -E 's/^(.+):([0-9]+):([0-9]+): (warning|error): (.+)/::error file=\1,line=\2,col=\3::\5/'
This produces inline annotations:
src/geometry.cpp
Line 42: ⚠️ warning: variable 'x' is not initialized [cppcoreguidelines-init-variables]
SonarQube/SonarCloud: Quality Gates
For larger projects, dedicated platforms provide dashboards:
| Metric | Description | Typical Threshold |
|---|---|---|
| Code Coverage | Percentage of code executed by tests | > 80% |
| Duplicated Lines | Copy-pasted code blocks | < 3% |
| Code Smells | Maintainability issues | A rating (best) |
| Bugs | Likely runtime errors | 0 new bugs |
| Vulnerabilities | Security issues | 0 new vulnerabilities |
Codecov: Coverage Tracking
- name: Generate coverage report
run: |
cmake --build build --target coverage
# Generates lcov.info
- name: Upload to Codecov
uses: codecov/codecov-action@v4
with:
files: build/coverage/lcov.info
fail_ci_if_error: true
Codecov shows:
- Coverage per file
- Coverage trends over time
- PR impact (“this PR decreases coverage by 2%”)
GitHub Status Checks: The Bottom Line
All these tools can report status to GitHub:
Pull Request #42: Add intersection calculation
┌────────────────────────────────────────────┐
│ ✅ build (ubuntu-latest) 2m 34s │
│ ✅ clang-tidy 1m 12s │
│ ✅ clang-format 0m 08s │
│ ✅ tests (ubuntu-latest) 0m 45s │
│ ✅ codecov/patch 80.5% │
│ ✅ codecov/project 85.2% │
└────────────────────────────────────────────┘
All checks have passed
Comparison: Python vs C++ Monitoring
| Aspect | Python | C++ |
|---|---|---|
| Linting | Ruff (fast, integrated) | clang-tidy (comprehensive) |
| Formatting | Ruff format | clang-format |
| Type checking | Pyright | Compiler (built-in) |
| Coverage | pytest-cov | gcov / llvm-cov |
| CI integration | GitHub Actions + Codecov | GitHub Actions + Codecov |
6.4 Compiler Warnings as Analysis
The C++ compiler itself is your first static analyzer. Different compilers have different warning options.
6.5 Compiler Warning Options
GCC:
# Basic warnings (recommended minimum)
g++ -Wall -Wextra -c main.cpp
# Pedantic (strict standard compliance)
g++ -Wall -Wextra -Wpedantic -c main.cpp
# Warnings as errors (CI configuration)
g++ -Wall -Wextra -Wpedantic -Werror -c main.cpp
Clang:
# Recommended Clang warnings
clang++ -Wall -Wextra -Wpedantic -c main.cpp
MSVC (Windows):
# Warning level 4 (highest recommended)
cl /W4 /WX main.cpp
6.6 Compiler Warning Summary
| Purpose | GCC | Clang | MSVC |
|---|---|---|---|
| Standard warnings | -Wall -Wextra |
-Wall -Wextra |
/W4 |
| Strict standards | -Wpedantic |
-Wpedantic |
/permissive- |
| Warnings as errors | -Werror |
-Werror |
/WX |
6.7 CMake Configuration for Compiler Warnings
# CMakeLists.txt
if(MSVC)
add_compile_options(/W4 /WX)
else()
add_compile_options(-Wall -Wextra -Wpedantic -Werror)
endif()
7. Testing Frameworks for C++
In Python, you use pytest. In C++, the equivalent is Google Test (gtest). Just as Ruff is the de facto Python linter, Google Test is the de facto C++ testing framework.
7.1 How Google Test Relates to pytest
Think of Google Test as “pytest for C++”:
| Aspect | Python (pytest) | C++ (Google Test) |
|---|---|---|
| Purpose | Run tests, report failures | Run tests, report failures |
| Installation | uv add pytest --dev |
CMake FetchContent |
| Config file | pyproject.toml |
CMakeLists.txt |
| Run tests | pytest |
ctest |
| Verbose output | pytest -v |
ctest --output-on-failure |
| Run specific test | pytest -k "test_name" |
ctest -R "test_name" |
| Maintainer | Community |
Google Test is:
- Open source, actively maintained by Google
- Cross-platform (Linux, macOS, Windows)
- Integrated with CMake (like pytest integrates with uv)
- Used by major projects (Chromium, LLVM, TensorFlow)
7.2 Installation Comparison
Python: Add to dev dependencies in pyproject.toml:
uv add pytest --dev
C++: Add to CMakeLists.txt using FetchContent:
# CMakeLists.txt
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(tests
tests/test_geometry.cpp
)
target_link_libraries(tests PRIVATE
geometry_core
GTest::gtest_main
)
include(GoogleTest)
gtest_discover_tests(tests)
Key insight: Both use a “declare dependencies, let the tool fetch them” pattern. uv sync downloads pytest; cmake downloads Google Test.
7.3 Test Structure Comparison
Side-by-side comparison of equivalent tests:
Python (tests/test_geometry.py):
import pytest
from road_profile_viewer.geometry import calculate_ray_line
def test_calculate_ray_line_basic():
"""Test basic ray calculation."""
result = calculate_ray_line(45.0, 0.0, 2.0, 80.0)
assert len(result["x"]) == 2
assert len(result["y"]) == 2
def test_calculate_ray_line_horizontal():
"""Test horizontal ray (0 degrees)."""
result = calculate_ray_line(0.0, 0.0, 2.0, 80.0)
# Horizontal ray: y stays constant
assert result["y"][0] == result["y"][1]
C++ (tests/test_geometry.cpp):
#include <gtest/gtest.h>
#include "geometry.hpp"
namespace rpv = road_profile_viewer;
// Test basic ray calculation
TEST(GeometryTest, CalculateRayLineBasic) {
auto result = rpv::calculate_ray_line(45.0, 0.0, 2.0, 80.0);
EXPECT_EQ(result.x.size(), 2);
EXPECT_EQ(result.y.size(), 2);
}
// Test horizontal ray (0 degrees)
TEST(GeometryTest, CalculateRayLineHorizontal) {
auto result = rpv::calculate_ray_line(0.0, 0.0, 2.0, 80.0);
// Horizontal ray: y stays constant
EXPECT_DOUBLE_EQ(result.y[0], result.y[1]);
}
Pattern mapping:
def test_xxx():→TEST(SuiteName, TestName)assert condition→EXPECT_TRUE(condition)assert x == y→EXPECT_EQ(x, y)- Docstrings → Comments (or use
SCOPED_TRACE())
7.4 Assertions and Expectations
| pytest | Google Test | Behavior on Failure |
|---|---|---|
assert x == y |
EXPECT_EQ(x, y) |
Continue test |
assert x == y (critical) |
ASSERT_EQ(x, y) |
Stop test immediately |
assert x != y |
EXPECT_NE(x, y) |
Continue test |
assert x < y |
EXPECT_LT(x, y) |
Continue test |
assert x > y |
EXPECT_GT(x, y) |
Continue test |
assert abs(x - y) < 0.001 |
EXPECT_NEAR(x, y, 0.001) |
Continue test |
assert x == pytest.approx(y) |
EXPECT_DOUBLE_EQ(x, y) |
Continue test |
assert x is None |
EXPECT_FALSE(x.has_value()) |
Continue test |
assert x is not None |
ASSERT_TRUE(x.has_value()) |
Stop if None (can't continue) |
EXPECT vs ASSERT - The Key Difference:
In pytest, every assert stops the test on failure. Google Test gives you a choice:
EXPECT_*: Log failure but continue (report all problems)ASSERT_*: Log failure and stop (later code would crash)
TEST(GeometryTest, FindIntersection) {
auto result = rpv::find_intersection(x_road, y_road, 10.0);
// ASSERT: Must pass before we can access result->x
ASSERT_TRUE(result.has_value()); // Stop if None
// EXPECT: Can check multiple properties
EXPECT_GT(result->x, 0.0); // Continue even if fails
EXPECT_GT(result->distance, 0.0); // Still runs
}
7.5 Test Fixtures Comparison
Both pytest and Google Test support fixtures for shared setup.
Python (pytest fixtures):
import pytest
from road_profile_viewer.geometry import find_intersection
@pytest.fixture
def road_profile():
"""Shared test data."""
return {
"x": [0, 10, 20, 30, 40],
"y": [0, 2, 4, 3, 5]
}
def test_find_intersection_exists(road_profile):
result = find_intersection(
road_profile["x"],
road_profile["y"],
angle=10.0
)
assert result is not None
assert result["distance"] > 0
def test_find_intersection_no_hit(road_profile):
# Ray pointing up, never hits road
result = find_intersection(
road_profile["x"],
road_profile["y"],
angle=-45.0 # Pointing upward
)
assert result is None
C++ (Google Test fixtures):
class GeometryTest : public ::testing::Test {
protected:
void SetUp() override {
// Shared test data (like @pytest.fixture)
x_road = {0, 10, 20, 30, 40};
y_road = {0, 2, 4, 3, 5};
}
// Fixture data available to all tests
std::vector<double> x_road;
std::vector<double> y_road;
};
// Use TEST_F (F = Fixture) instead of TEST
TEST_F(GeometryTest, FindIntersectionExists) {
auto result = rpv::find_intersection(
x_road, y_road, 10.0, 0.0, 10.0
);
ASSERT_TRUE(result.has_value());
EXPECT_GT(result->distance, 0.0);
}
TEST_F(GeometryTest, FindIntersectionNoHit) {
// Ray pointing up, never hits road
auto result = rpv::find_intersection(
x_road, y_road, -45.0, 0.0, 10.0
);
EXPECT_FALSE(result.has_value());
}
Pattern mapping:
@pytest.fixture→class TestName : public ::testing::Test- Fixture function →
SetUp()method def test_xxx(fixture):→TEST_F(ClassName, TestName)
7.6 Parametrized Tests Comparison
Both frameworks support running the same test with different inputs.
Python (@pytest.mark.parametrize):
import pytest
@pytest.mark.parametrize("angle,expected_hits", [
(10.0, True), # Shallow angle, hits road
(45.0, True), # Steep angle, hits road
(-10.0, False), # Upward angle, misses
(90.0, False), # Vertical, edge case
])
def test_intersection_angles(road_profile, angle, expected_hits):
result = find_intersection(
road_profile["x"],
road_profile["y"],
angle=angle
)
assert (result is not None) == expected_hits
C++ (Google Test INSTANTIATE_TEST_SUITE_P):
struct AngleTestCase {
double angle;
bool expected_hits;
};
class AngleTest : public ::testing::TestWithParam<AngleTestCase> {
protected:
void SetUp() override {
x_road = {0, 10, 20, 30, 40};
y_road = {0, 2, 4, 3, 5};
}
std::vector<double> x_road;
std::vector<double> y_road;
};
TEST_P(AngleTest, IntersectionAngles) {
auto [angle, expected_hits] = GetParam();
auto result = rpv::find_intersection(
x_road, y_road, angle, 0.0, 10.0
);
EXPECT_EQ(result.has_value(), expected_hits);
}
INSTANTIATE_TEST_SUITE_P(
GeometryAngles,
AngleTest,
::testing::Values(
AngleTestCase{10.0, true}, // Shallow angle
AngleTestCase{45.0, true}, // Steep angle
AngleTestCase{-10.0, false}, // Upward angle
AngleTestCase{90.0, false} // Vertical
)
);
Key insight: The concept is identical—run the same test logic with different data. pytest is more concise; Google Test is more explicit.
7.7 Running Tests Comparison
| Action | Python (pytest) | C++ (ctest/gtest) |
|---|---|---|
| Run all tests | pytest |
ctest --test-dir build |
| Verbose output | pytest -v |
ctest --output-on-failure |
| Run by name pattern | pytest -k "ray" |
ctest -R "ray" |
| Run specific file | pytest tests/test_geometry.py |
./build/tests --gtest_filter="Geometry*" |
| Stop on first failure | pytest -x |
ctest --stop-on-failure |
| Parallel execution | pytest -n auto |
ctest -j$(nproc) |
Build and run workflow:
# Python
uv run pytest -v
# C++
cmake --build build
ctest --test-dir build --output-on-failure
# Or run the test executable directly for more details
./build/tests
7.8 Test Output Comparison
pytest output:
========================= test session starts ==========================
collected 4 items
tests/test_geometry.py::test_calculate_ray_line_basic PASSED [ 25%]
tests/test_geometry.py::test_calculate_ray_line_horizontal PASSED [ 50%]
tests/test_geometry.py::test_find_intersection_exists PASSED [ 75%]
tests/test_geometry.py::test_find_intersection_no_hit PASSED [100%]
========================== 4 passed in 0.12s ===========================
Google Test output:
[==========] Running 4 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 4 tests from GeometryTest
[ RUN ] GeometryTest.CalculateRayLineBasic
[ OK ] GeometryTest.CalculateRayLineBasic (0 ms)
[ RUN ] GeometryTest.CalculateRayLineHorizontal
[ OK ] GeometryTest.CalculateRayLineHorizontal (0 ms)
[ RUN ] GeometryTest.FindIntersectionExists
[ OK ] GeometryTest.FindIntersectionExists (0 ms)
[ RUN ] GeometryTest.FindIntersectionNoHit
[ OK ] GeometryTest.FindIntersectionNoHit (0 ms)
[----------] 4 tests from GeometryTest (0 ms total)
[==========] 4 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 4 tests.
The output format differs, but the information is the same: Which tests ran, which passed, which failed, and how long it took.
8. Coverage Reports for C++
In Python, you use pytest-cov to measure test coverage. In C++, the equivalent tools are gcov (GCC) and llvm-cov (Clang). The workflow is the same: instrument code, run tests, generate report.
8.1 How C++ Coverage Relates to pytest-cov
| Aspect | Python (pytest-cov) | C++ (gcov/llvm-cov) |
|---|---|---|
| Installation | uv add pytest-cov --dev |
Built into GCC/Clang |
| Enable coverage | pytest --cov=src |
CMake: -DENABLE_COVERAGE=ON |
| Run with coverage | pytest --cov=src |
ctest (after coverage build) |
| Terminal report | --cov-report=term |
gcov or llvm-cov report |
| HTML report | --cov-report=html |
genhtml (lcov) or llvm-cov show |
| CI upload | Codecov action | Codecov action (same!) |
8.2 Coverage Workflow Comparison
Python workflow:
# Install
uv add pytest-cov --dev
# Run tests with coverage
uv run pytest --cov=src --cov-report=term --cov-report=html
# View HTML report
open htmlcov/index.html
C++ workflow:
# Configure with coverage enabled
cmake -B build -DENABLE_COVERAGE=ON
# Build
cmake --build build
# Run tests (this generates coverage data)
ctest --test-dir build
# Generate HTML report
lcov --capture --directory build --output-file coverage.info
genhtml coverage.info --output-directory htmlcov
# View HTML report
open htmlcov/index.html
Key insight: The steps are conceptually identical:
- Enable coverage instrumentation
- Run tests
- Generate human-readable report
8.3 CMake Coverage Configuration
Add this to your CMakeLists.txt:
# CMakeLists.txt
option(ENABLE_COVERAGE "Enable code coverage" OFF)
if(ENABLE_COVERAGE)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
# Add coverage flags (like pytest --cov)
add_compile_options(--coverage -O0 -g)
add_link_options(--coverage)
endif()
endif()
Comparison with pyproject.toml:
# pyproject.toml (Python)
[tool.pytest.ini_options]
addopts = "--cov=src --cov-report=term"
Both configure coverage in the project’s build configuration file.
8.4 gcov (GCC) vs llvm-cov (Clang)
| Aspect | gcov (GCC) | llvm-cov (Clang) |
|---|---|---|
| Compiler | GCC (g++) | Clang (clang++) |
| Coverage flags | --coverage |
-fprofile-instr-generate -fcoverage-mapping |
| Raw data format | .gcda, .gcno |
.profraw |
| HTML generator | lcov + genhtml | llvm-cov show --format=html |
| CI compatibility | Codecov, Coveralls | Codecov, Coveralls |
gcov workflow (more common in Linux):
# Build with GCC coverage
cmake -B build -DCMAKE_CXX_COMPILER=g++ -DENABLE_COVERAGE=ON
cmake --build build
# Run tests
ctest --test-dir build
# Generate lcov report
lcov --capture --directory build --output-file coverage.info
lcov --remove coverage.info '/usr/*' '*/tests/*' --output-file coverage.info
# Generate HTML
genhtml coverage.info --output-directory htmlcov
llvm-cov workflow (better source annotation):
# Build with Clang coverage
cmake -B build -DCMAKE_CXX_COMPILER=clang++ -DENABLE_COVERAGE=ON
cmake --build build
# Run tests (generates .profraw)
ctest --test-dir build
# Merge profile data
llvm-profdata merge -sparse default.profraw -o coverage.profdata
# Generate report
llvm-cov show ./build/tests -instr-profile=coverage.profdata --format=html > htmlcov/index.html
8.5 Coverage Report Comparison
pytest-cov terminal output:
---------- coverage: platform linux, python 3.12.0 ----------
Name Stmts Miss Cover
-----------------------------------------------------
src/road_profile_viewer/__init__.py 2 0 100%
src/road_profile_viewer/geometry.py 45 3 93%
src/road_profile_viewer/visualization.py 78 12 85%
-----------------------------------------------------
TOTAL 125 15 88%
gcov/lcov terminal output:
Reading tracefile coverage.info
Summary coverage rate:
lines......: 88.0% (110 of 125 lines)
functions..: 100.0% (12 of 12 functions)
branches...: 75.0% (30 of 40 branches)
Both show the same key metric: What percentage of your code was executed by tests.
8.6 CI Integration Comparison
Python CI (.github/workflows/python-ci.yml):
- name: Run tests with coverage
run: uv run pytest --cov=src --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage.xml
fail_ci_if_error: true
C++ CI (.github/workflows/cpp-ci.yml):
- name: Configure with coverage
run: cmake -B build -DENABLE_COVERAGE=ON
- name: Build
run: cmake --build build
- name: Run tests
run: ctest --test-dir build
- name: Generate coverage report
run: |
lcov --capture --directory build --output-file coverage.info
lcov --remove coverage.info '/usr/*' '*/tests/*' -o coverage.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage.info
fail_ci_if_error: true
Key insight: Both upload to the same Codecov service. Your Python and C++ coverage reports appear on the same dashboard, making it easy to track overall project health.
8.7 Coverage Thresholds
Python (in pyproject.toml or pytest.ini):
[tool.coverage.report]
fail_under = 80
C++ (in CI workflow):
- name: Check coverage threshold
run: |
COVERAGE=$(lcov --summary coverage.info | grep "lines" | grep -oP '\d+\.\d+')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Coverage $COVERAGE% is below 80% threshold"
exit 1
fi
Or use Codecov’s built-in threshold:
# codecov.yml (same for Python and C++)
coverage:
status:
project:
default:
target: 80%
threshold: 1%
8.8 The Coverage Workflow Summary
uv add pytest-covpytest --covcoverage.xmlcmake -DCOVERAGE=ONctestlcov→ coverage.info9. Comparison: Python vs. C++ Quality Tools
Now that we’ve covered code quality, testing, and coverage for both ecosystems, let’s put it all together. This comprehensive comparison shows how the discipline you learned in Python transfers directly to C++.
9.1 Tool Comparison
| Function | Python | C++ |
|---|---|---|
| Style Guide | PEP 8 | Google C++ Style Guide |
| Formatter | Ruff format, Black | clang-format |
| Linter | Ruff check, Pylint | clang-tidy |
| Type Checker | Pyright, mypy | Compiler (built-in) |
| Config File | pyproject.toml |
.clang-format + .clang-tidy |
| Import Sorting | isort (part of Ruff) | clang-format IncludeCategories |
| Pre-commit Hook | ruff check --fix && ruff format |
clang-format -i && clang-tidy |
9.2 Workflow Comparison
| Workflow Step | Python | C++ |
|---|---|---|
| Format all files | ruff format . |
find . -name '*.cpp' | xargs clang-format -i |
| Check formatting (CI) | ruff format --check . |
clang-format --dry-run --Werror |
| Run linter | ruff check . |
clang-tidy -p build src/*.cpp |
| Fix linter issues | ruff check --fix . |
clang-tidy -p build --fix src/*.cpp |
| Type check | pyright . |
(Compiler does this automatically) |
| IDE integration | VS Code Python extension | VS Code C/C++ extension, clangd |
9.3 Configuration Philosophy
| Aspect | Python | C++ |
|---|---|---|
| Config location | One file: pyproject.toml |
Separate files per tool |
| Config format | TOML | YAML (clang tools) |
| Base style | PEP 8 (implicit default) | BasedOnStyle: Google (explicit) |
| Rule selection | select = ["E", "W", "F"] |
Checks: bugprone-*, performance-* |
| Disable rules | ignore = ["E501"] |
-readability-magic-numbers |
9.4 CI/CD Integration
| CI Step | Python (GitHub Actions) | C++ (GitHub Actions) |
|---|---|---|
| Setup | uses: astral-sh/setup-uv@v4 |
sudo apt install clang-format clang-tidy |
| Format check | uv run ruff format --check . |
clang-format --dry-run --Werror src/*.cpp |
| Lint check | uv run ruff check . |
clang-tidy -p build src/*.cpp |
| Type check | uv run pyright |
(Part of cmake --build) |
| Exit on failure | Automatic (non-zero exit) | --warnings-as-errors='*' |
9.5 Testing Comparison
| Aspect | Python (pytest) | C++ (Google Test) |
|---|---|---|
| Install | uv add pytest --dev |
CMake FetchContent |
| Run tests | pytest |
ctest --test-dir build |
| Verbose | pytest -v |
ctest --output-on-failure |
| Filter tests | pytest -k "ray" |
ctest -R "ray" |
| Assertions | assert x == y |
EXPECT_EQ(x, y) |
| Fixtures | @pytest.fixture |
class : public ::testing::Test |
| Parametrize | @pytest.mark.parametrize |
INSTANTIATE_TEST_SUITE_P |
9.6 Coverage Comparison
| Aspect | Python (pytest-cov) | C++ (gcov/llvm-cov) |
|---|---|---|
| Install | uv add pytest-cov --dev |
Built into GCC/Clang |
| Enable | pytest --cov=src |
cmake -DENABLE_COVERAGE=ON |
| HTML report | --cov-report=html |
genhtml coverage.info |
| CI upload | codecov/codecov-action@v4 |
codecov/codecov-action@v4 |
| Threshold | fail_under = 80 |
codecov.yml: target: 80% |
9.7 The Key Insight
┌─────────────────────────────────────────────────────────────────────┐
│ THE SAME DISCIPLINE, DIFFERENT TOOLS │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ CODE QUALITY: │
│ Python: pyproject.toml → Ruff format → Ruff check → CI │
│ C++: .clang-format → clang-format → clang-tidy → CI │
│ │
│ TESTING: │
│ Python: pytest → assert x == y → @pytest.fixture │
│ C++: ctest → EXPECT_EQ(x,y) → ::testing::Test │
│ │
│ COVERAGE: │
│ Python: pytest --cov → htmlcov/ → Codecov │
│ C++: gcov/llvm-cov → genhtml → Codecov │
│ │
│ The tools are different, but the workflow is identical: │
│ 1. Configure once in project root │
│ 2. Run automatically (pre-commit, CI) │
│ 3. Consistent, high-quality, well-tested code │
│ │
└─────────────────────────────────────────────────────────────────────┘
What you learned in Python transfers directly to C++. The same mental model—configure tools, enforce automatically, test thoroughly, measure coverage—applies in both ecosystems. You’re not learning something new; you’re applying familiar concepts with different syntax.
10. Summary
In this lecture, you learned how to apply the same quality discipline to C++ that you already use for Python:
10.1 What You’ve Learned
Code Formatting:
- clang-format automatically enforces consistent style (like Black/Ruff for Python)
- The Google C++ Style Guide provides a well-documented standard
- Configuration lives in
.clang-formatat project root
Static Analysis:
- Compiler warnings (
-Wall -Wextra -Wpedantic) catch many bugs at compile time - clang-tidy provides deeper analysis and modernization checks
- Configuration lives in
.clang-tidyat project root
Unit Testing:
- Google Test provides a pytest-like testing experience for C++
EXPECT_*continues on failure,ASSERT_*stops immediately- Test fixtures share setup/teardown across related tests
Coverage:
- gcov/llvm-cov measure which code your tests execute
- Same workflow as Python: run tests with coverage flags, generate HTML report
- Codecov works identically for Python and C++ coverage
Key Insight:
The tools are different, but the discipline is identical:
Configure → Enforce → Test → Measure → Repeat
11. Reflection Questions
Before moving on to Part 3, consider these questions:
-
Configuration: How would you set up clang-format to match your team’s existing code style?
-
Warnings as Errors: What are the pros and cons of using
-Werrorin production vs. development builds? -
Test Design: When would you use
EXPECT_*vs.ASSERT_*in Google Test? Give an example of each. -
Coverage Gaps: If a function has 100% line coverage but still has a bug, what might be missing? How does branch coverage help?
-
Tool Comparison: You’re joining a new team that uses C++. How would you explain the purpose of clang-tidy to a Python developer who knows Ruff?
12. What’s Next
Part 3: Hands-On Implementation will show you how to combine Python and C++ in a single project:
- Mono-repo architecture — Managing Python and C++ in one repository
- pybind11 — Creating Python bindings for C++ functions
- Hands-on implementation — Porting geometry functions from Road Profile Viewer to C++
- CI/CD — GitHub Actions for multi-language projects
By the end of Part 3, your Road Profile Viewer will have C++ implementations that:
- Run 10-100x faster than pure Python
- Can be deployed to embedded systems
- Integrate seamlessly with your existing Python code
┌─────────────────────────────────────────────────────────────────────┐
│ WHAT'S COMING IN PART 3 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Python Code C++ Code │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ calculate_ray_line │ │ calculate_ray_line │ │
│ │ find_intersection │ ── pybind11 ──> │ find_intersection │ │
│ │ (easy to read) │ │ (fast to run) │ │
│ └────────────────────┘ └────────────────────┘ │
│ │
│ Your Python app calls C++ functions as if they were native Python │
│ │
└─────────────────────────────────────────────────────────────────────┘