v = {1, 2, 3};
for (auto x : v) {
return x; // MSVC does NOT warn here
}
```
Why? Because with `vector`, the compiler can prove the loop body runs at most once via other means. With a **generator** (custom iterator), the analysis is more conservative.
---
class: nord-light, middle, center
## ๐ก Three Approaches
---
### Approach 1: Range-for + `#pragma` ๐
```cpp
#pragma warning(push)
#pragma warning(disable : 4702)
for (const auto vtx : this->_find_cycle()) {
assert(...);
return cycle_list(vtx);
}
#pragma warning(pop)
```
.pull-left[
**Pros** โ
- Keeps idiomatic range-for
- Minimal change
- Explicit about intent
]
.pull-right[
**Cons** โ
- Suppression is a "confession"
- `#pragma` pollution
- Might hide real issues
]
---
### Approach 2: Explicit Iterator ๐ง
```cpp
auto gen = this->_find_cycle();
auto it = gen.begin();
if (it != gen.end()) {
assert(this->_is_negative_node_pairs(*it, dist, get_weight));
return this->_cycle_list_node_pairs(*it);
}
```
**Pros** โ
- No warning, no pragma
- Explicit: "take first element"
**Cons** โ
- **Assumes `py::Generator` has `begin()`/`end()` as separate calls** โ not guaranteed
- May force coroutine to eagerly evaluate first element at `begin()` call site
- More verbose, less idiomatic
- Changes the semantics subtly
---
### Approach 3: Keep Range-for, Suppress โ
**This is what we chose.**
```cpp
while (this->_relax_node_pairs(dist, get_weight)) {
for (const auto vtx : this->_find_cycle()) {
assert(this->_is_negative_node_pairs(vtx, dist, get_weight));
return this->_cycle_list_node_pairs(vtx);
}
}
```
Backed by `#pragma warning(push/pop)` at file scope.
**Why this is the right call:**
- The pragma is scoped to the entire file (not inline noise)
- Range-for is the **canonical** way to consume a generator
- No semantic change, no risk
- The "unreachable" code is compiler-generated boilerplate, not programmer logic
---
class: nord-light, middle, center
## ๐ง Analysis: Bug or Feature?
---
### MSVC's Perspective ๐ฆ
> "The loop increment and condition check after `return` can never execute. I must warn."
MSVC is **technically correct**:
```cpp
// Compiler-generated bookkeeping โ literally cannot run
++__begin; // โ unreachable
__begin != __end; // โ unreachable
```
The C++ standard says: "If a statement is unreachable, a diagnostic may be issued."
MSVC chooses to diagnose this. It's **not a bug** โ it's following the rules.
---
### The QoI Argument ๐ฏ
But **Quality of Implementation** matters:
| Perspective | Verdict |
|---|---|
| **Language lawyer** | โ
MSVC is correct โ dead code exists |
| **Practical developer** | โ This is noise โ the pattern is intentional |
| **GCC/Clang** | Silent โ they recognize the idiom |
| **C++ committee intent** | Range-for is a *vocabulary construct* โ warning about its expansion is pedantic |
The real issue: **MSVC warns about compiler-generated code, not programmer code.**
---
### What Other Compilers Do ๐
.mermaid[
graph TB
subgraph Source["Same Source"]
A("range-for + return\nover generator")
end
subgraph GCC["GCC 13"]
B("โ
Silent")
end
subgraph Clang["Clang 17"]
C("โ
Silent")
end
subgraph MSVC["MSVC 2022"]
D("โ ๏ธ C4702")
end
Source --> GCC
Source --> Clang
Source --> MSVC
style A fill:#ff9800,color:#fff
style B fill:#4caf50,color:#fff
style C fill:#4caf50,color:#fff
style D fill:#f44336,color:#fff
]
MSVC stands alone here. This suggests it's not a language issue โ it's a **compiler heuristic** that could be improved.
---
### The Real Distinction ๐ฏ
**Unreachable programmer logic** โ **Unreachable compiler-generated boilerplate**
| Kind | Example | Should warn? |
|---|---|---|
| **Programmer logic** | `if (false) { ... }` | โ
Yes โ likely a mistake |
| **Compiler-generated** | Range-for expansion after `return` | โ No โ noise |
| **Unreachable branch** | `switch` with all cases covered | โ ๏ธ Maybe โ depends |
| **Dead code guard** | `if (computed_value) { ... }` after assert | โ No โ defensive |
The `#pragma` suppression isn't hiding a real bug. It's telling the compiler: "I know about those invisible loop internals, and they're fine."
---
class: nord-light, middle, center
## ๐ Resolution
---
### What We Kept โ
```cpp
// neg_cycle.hpp / neg_cycle_q.hpp โ file-level pragma
#pragma warning(push)
#pragma warning(disable : 4702)
// ... entire file with range-for + return patterns
#pragma warning(pop)
```
**Also added to CMakeLists.txt** for good measure:
```cmake
/wd4702 # suppress C4702: unreachable code in generator patterns
```
Wait โ actually we only added the `#pragma`. The `/wd4702` was NOT added globally, because the pragma is sufficient and more targeted.
---
### Why This Is Correct ๐ฏ
1. **The pattern is intentional** โ take first yield, return it
2. **The dead code is compiler-generated** โ not programmer logic
3. **Other compilers don't warn** โ MSVC's diagnostic is overly pedantic
4. **The pragma is scoped** โ not suppressing warnings project-wide
5. **No semantic alternatives** โ explicit iterator would change generator consumption semantics
> **"It's not a bug. It's not a feature. It's a QoI gap."**
---
### Lessons Learned ๐
1. **MSVC treats C4702 aggressively** โ especially with `/WX`
2. **Generator + range-for + return** is a valid pattern, but MSVC flags it
3. **`#pragma warning`** is the pragmatic fix โ scoped, intentional, minimal
4. **Compiler diagnostics vary** โ what's noise on one compiler is an error on another
5. **Know when to fight** โ restructuring code to silence a compiler is sometimes worse than suppressing
.mermaid[
graph LR
A("Write code") --> B{"Compiler\nwarns?"}
B -->|"Real issue"| C("Fix the code")
B -->|"False positive"| D{"Other compilers\nsilent?"}
D -->|"Yes"| E("Suppress +\ncomment why")
D -->|"No"| F("Research more")
style A fill:#2196f3,color:#fff
style B fill:#ff9800,color:#fff
style C fill:#4caf50,color:#fff
style D fill:#9c27b0,color:#fff
style E fill:#f44336,color:#fff
style F fill:#607d8b,color:#fff
]
---
### The Code That Lives On ๐ป
```cpp
auto find_neg_cycle(Mapping& dist, Callable&& get_weight)
-> std::vector>
{
this->_pred.clear();
if constexpr (requires { this->_digraph.size(); }) {
this->_pred.reserve(this->_digraph.size());
}
while (this->_relax_node_pairs(dist, get_weight)) {
for (const auto vtx : this->_find_cycle()) {
assert(this->_is_negative_node_pairs(vtx, dist, get_weight));
return this->_cycle_list_node_pairs(vtx);
}
}
return {};
}
```
Still idiomatic. Still generates C4702. **Still the right code.** ๐ช
---
class: nord-light, middle, center
## ๐ญ Final Thoughts
---
### The Deeper Question ๐ค
> **"C++ gives you enough rope to hang yourself โ and also to build a suspension bridge."**
The generator + range-for + return pattern is a **suspension bridge**: it's elegant, efficient, and intentional. But MSVC's C4702 treats the loose threads as if you're about to hang yourself.
The lesson isn't about who's right or wrong. It's about knowing your tools:
- ๐ Understand what range-for expands to
- ๐ Know when a warning is about *your* code vs *the compiler's* code
- ๐ ๏ธ Have a toolbox of responses (pragma, restructure, suppress globally)
- ๐ง Choose the fix that communicates intent best
---
### The Final Scorecard ๐
| Question | Answer |
|---|---|
| Is MSVC wrong? | โ No โ there IS unreachable code |
| Is the pattern wrong? | โ No โ it's idiomatic and intentional |
| Should we restructure? | โ No โ that obscures intent |
| Is the `#pragma` a hack? | โ ๏ธ Partially โ but it's scoped and justified |
| Is this a compiler bug? | โ ๏ธ QoI gap, not a bug |
| Would we change it? | โ No โ current code stays |
---
### References ๐
- **MSVC C4702**: [learn.microsoft.com](https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4702)
- **CPPCoreGuidelines**: [isocpp.github.io](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)
- **py2cpp Generator**: `py2cpp/gen.hpp` โ coroutine-based generator
- **digraphx-cpp**: `digraphx-cpp/include/digraphx/neg_cycle.hpp`
- **netoptim-cpp**: `netoptim-cpp/include/netoptim/network_oracle.hpp`
---
count: false
class: nord-dark, middle, center
# ๐ค Q&A
## Thank You! ๐
@luk036 ๐จโ๐ป ยท 2026 ๐
**Slides**: `proglang/bug-or-feature-remark.html`
---
count: false
class: nord-dark, middle, center
# ๐ Bonus: The Full Fix History
---
### What Actually Changed ๐
Three files modified in `digraphx-cpp`:
**`include/digraphx/neg_cycle.hpp`**
```
-#include
+#include
+#include
...
+#pragma warning(push)
+#pragma warning(disable : 4702)
...
+#pragma warning(pop)
```
**`include/digraphx/neg_cycle_q.hpp`** โ same pragma wrapping
**`CMakeLists.txt`** โ no change needed (pragma is sufficient)
---
### Full Build Verification โ
```bash
# digraphx-cpp โ cmake
$ cmake --build build --config Debug -j4
# 47 doctest tests + 1600 rapidcheck tests โ all pass ๐
# netoptim-cpp โ cmake
$ cmake --build build --config Debug -j4
# 17 doctest tests โ all pass ๐
# Both projects โ xmake
$ xmake -y -j 10
# All tests pass on both build systems ๐
```
**Zero warnings, zero errors, zero semantic changes.** ๐