C++ Part I · Module 1 · Why C++ Matters in Semiconductor Engineering
Which C++ Are We Learning: Standards, Compilers and Extensions
The sentence 'C++ does X' is incomplete, and the missing half causes real bugs. Three different things get called C++: what the ISO standard guarantees, what the standard deliberately declines to pin down, and what the particular compiler in front of you adds on top. An engineer who cannot separate those three writes code that works on their machine, fails in a colleague's simulator, and leaves nobody able to say which of the two was wrong. This chapter fixes the baseline this curriculum teaches — C++17, with later features labelled wherever they appear — and then builds the vocabulary that makes every later claim attributable: the four grades of 'it depends', from a result the standard simply does not choose, through one your compiler must document, to one that strips your program of meaning entirely. The last of those is demonstrated rather than described: one function, one compiler, two optimisation levels, two opposite answers.
Foundation17 min readC++Part IFundamentalsStandards
Part I · Module 1 · Chapter 1.3 · Which C++ Are We Learning
1. Three Things Are Called "C++"
Chapter 1.1 established that C++ is defined by a written standard and that a compiler implements it. That is true and it is not yet enough to work with, because in practice three distinct things travel under the name C++:
- The language the ISO standard defines — and the standard has several versions, which differ in ways that matter.
- The parts the standard deliberately leaves open — and it leaves them open in three different grades, with very different consequences.
- What your compiler accepts — which is the standard, plus extensions it added, minus anything it has not finished implementing.
When someone says "this is valid C++", they could mean any of the three. When a bug reproduces on one machine and not another, the explanation is almost always that someone relied on (3) while believing they were relying on (1).
This chapter makes the three separable. It is the least glamorous chapter in the module and the one the other twenty-seven modules lean on hardest, because every technical claim this curriculum makes needs to be attributable to a specific one of those three.
2. The Versions, and What Each One Actually Changed
C++ has been revised on a three-year cycle since 2011. You do not need the history, but you do need to know roughly what arrived when, because a great deal of C++ advice on the internet is correct for a version you are not using.
| Version | Year | What it is remembered for |
|---|---|---|
| C++98 / C++03 | 1998 / 2003 | The first standard. Templates, the STL, exceptions. Everything had to be written out longhand. |
| C++11 | 2011 | The watershed. Move semantics, auto, range-based for, lambdas, smart pointers, nullptr, a threading model. "Modern C++" means C++11 and later. |
| C++14 | 2014 | A clean-up release. std::make_unique, generic lambdas, digit separators. Small, and worth having. |
| C++17 | 2017 | This curriculum's baseline. std::optional, std::variant, std::string_view, structured bindings, if with an initialiser, guaranteed copy elision in the cases that matter. |
| C++20 | 2020 | Concepts, ranges, three-way comparison, modules, coroutines. A large release whose toolchain support is still uneven in places. |
| C++23 | 2023 | Consolidation on C++20's additions. |
Two observations are worth more than the table.
C++11 is the real dividing line. Code written before it looks substantially different from code written after it, and the difference is not cosmetic — move semantics (Module 13) and smart pointers (Module 14) changed how resources are handled, not just how they are spelled. If you find a C++ tutorial that manages memory with raw new and delete throughout and never mentions ownership, you are reading pre-2011 material regardless of its publication date.
Later is not automatically better in practice. Which version you can use is decided by the toolchain you must build with, and in semiconductor work that toolchain is frequently not yours to choose.
3. Why This Curriculum Teaches C++17
The baseline is a deliberate decision with a specific justification, not a default.
C++17 is the newest standard you can reasonably assume is fully available. GCC, Clang and MSVC have complete, mature C++17 support and have had it for years. C++20's support is good in the major compilers but remains patchy in places, and — more to the point for this audience — it is frequently unavailable where the work happens.
That matters more in this field than in most. Code written for the applications in Chapters 1.5 and 1.6 often has to be compiled by somebody else's toolchain:
- A commercial simulator compiles the C++ you hand it across its DPI boundary using a compiler the vendor chose, which may be several years behind the one on your laptop.
- An EDA tool's plug-in interface fixes a standard level, and your code either meets it or does not load.
- A cross-compiler for an embedded target often lags the host toolchain considerably.
- A long-lived internal model may have to keep building on the toolchain that was current when the project started.
C++17 is the level at which "it compiled on my machine" and "it will compile on theirs" come closest to being the same statement. That is the whole argument.
What this means for you as a reader of this curriculum: every example is C++17 unless it says otherwise, and the small number of places where a later feature is genuinely the right answer are labelled explicitly. Two chapters later in Part I do this: Module 15.4 uses the C++20 three-way comparison operator, and Module 21.8 teaches concepts, a C++20 feature. Both say so. Nothing in this curriculum will quietly use a feature your toolchain may not have.
You can always ask a compiler which version it is applying, because the standard requires a macro that says so:
#include <iostream>
int main()
{
std::cout << __cplusplus << '\n';
}On the machine used to write this chapter — Apple clang 21 on arm64 — the two invocations that matter here produced:
clang++ -std=c++17 ver.cpp -o ver && ./ver → 201703
clang++ -std=c++20 ver.cpp -o ver && ./ver → 202002The value encodes the year and month the standard was finalised. It is the one reliable way to find out what the compiler thinks it is compiling, and it is worth knowing because the answer is sometimes not what the build system claims.
4. The Standard Does Not Describe Everything — On Purpose
Here is the idea that the rest of this chapter rests on, and that Chapter 1.1 promised.
The C++ standard does not specify the behaviour of every program you can write. For some constructs it declines, and the declining is deliberate: pinning down every detail would force every implementation onto the same machine model, which would make C++ unusable on the range of hardware it has to run on. Chapter 1.2 §4 listed "no managed runtime" as a design constraint; this is the other side of that coin. A language that gets close to the hardware has to let the hardware differ.
But "the standard declines" covers three genuinely different situations, and treating them as one is the mistake. Added to the normal case, there are four categories, and an engineer needs all four names.
5. The Four Grades of "It Depends"
| Category | What the standard says | Must the implementation document it? | Is your program meaningful? |
|---|---|---|---|
| Well-defined | Exactly what must happen | — | Yes |
| Unspecified | A choice from a set of permitted behaviours; the implementation need not say which | No | Yes, but you must not depend on which |
| Implementation-defined | A choice from a permitted set, and the implementation must document its choice | Yes | Yes, and you may depend on it once you have named the platform |
| Undefined | Nothing. No constraints whatsoever | — | No. The program has no meaning at all |
Each of the bottom three deserves a demonstration, because the distinctions become real once you have seen them behave.
Implementation-defined: how wide is a long?
The standard fixes minimum ranges for the integer types, not exact sizes. A conforming implementation chooses, and must tell you what it chose.
#include <climits>
#include <cstdint>
#include <iostream>
int main()
{
std::cout << "CHAR_BIT = " << CHAR_BIT << '\n';
std::cout << "sizeof(int) = " << sizeof(int) << '\n';
std::cout << "sizeof(long) = " << sizeof(long) << '\n';
std::cout << "sizeof(void*) = " << sizeof(void*) << '\n';
std::cout << "char is signed = " << std::boolalpha
<< (CHAR_MIN < 0) << '\n';
}On the machine used for this chapter, compiled -std=c++17 -Wall -Wextra -Wpedantic:
CHAR_BIT = 8
sizeof(int) = 4
sizeof(long) = 8
sizeof(void*) = 8
char is signed = trueEvery one of those five numbers is a property of this platform rather than of C++. The third is the famous one: sizeof(long) is 8 here and on Linux, and 4 on 64-bit Windows. Code that assumes a long holds a 64-bit value is correct on two of the three major platforms and silently truncating on the third.
The right conclusion is not "avoid long" but something more useful: when a width is part of the specification rather than a convenience, say the width. That is what std::uint32_t is for, and Module 3.4 makes it a rule for exactly this reason. It is also why Chapter 1.1's little RegisterCheck used std::uint32_t rather than unsigned — a 32-bit register is 32 bits on every platform, and the type should say so.
Whether plain char is signed is the other entry worth noticing; it is signed here, unsigned on some ARM Linux configurations, and Module 3.6 covers what that costs you.
Unspecified: in what order are arguments evaluated?
When you write f(g(), h()), the standard does not say whether g() or h() runs first. Both orders are permitted, and the compiler need not document or be consistent about its choice.
#include <iostream>
int next(int& counter) { return ++counter; }
void show(int a, int b) { std::cout << "a=" << a << " b=" << b << '\n'; }
int main()
{
int counter = 0;
show(next(counter), next(counter)); // which call runs first is unspecified
}Now the honest part, and the reason this example is more instructive than a dramatic one would be. On the machine used for this chapter, every invocation agreed:
clang++ -std=c++17 -O0 → a=1 b=2
clang++ -std=c++17 -O2 → a=1 b=2
g++ -std=c++17 → a=1 b=2And -Wall -Wextra -Wpedantic produced no warning at all.
That consistency is exactly the trap. A reader who runs this program, sees the same answer three times, and concludes "arguments evaluate left to right" has drawn a conclusion the standard does not support from evidence that looks conclusive. The next compiler, the next version, or the same compiler on a different target is entitled to print a=2 b=1, and no bug will have been introduced by anyone.
Unlike the undefined case below, nothing here is catastrophic: the program has a meaning, and it is one of two possible meanings. You simply must not write code that cares which.
Undefined: the category that removes meaning
This is the one that behaves unlike anything else, and the one where intuition fails hardest. Here is a function that looks obviously true:
#include <climits>
#include <iostream>
bool can_increment(int value) { return value + 1 > value; }
int main()
{
std::cout << std::boolalpha
<< "can_increment(5) = " << can_increment(5) << '\n'
<< "can_increment(INT_MAX) = " << can_increment(INT_MAX) << '\n';
}Adding one to a number makes it larger. For every int except one that holds INT_MAX, that is true. For INT_MAX, value + 1 overflows — and signed integer overflow is undefined behaviour in C++.
Compiled with the same compiler, the same source, at two optimisation levels:
clang++ -std=c++17 -O0 ub.cpp -o ub && ./ub
can_increment(5) = true
can_increment(INT_MAX) = false
clang++ -std=c++17 -O2 ub.cpp -o ub && ./ub
can_increment(5) = true
can_increment(INT_MAX) = trueRead that twice. The same function returned false at -O0 and true at -O2. Nothing is broken in the compiler, and neither answer is more correct than the other.
The mechanism is worth understanding because it is the mechanism behind most surprising UB:
- At
-O0, the compiler emitted an actual addition. The hardware wrapped around to a negative number, the comparison was performed on that, and the answer came outfalse. - At
-O2, the optimiser reasoned about the language, not the hardware. Since signed overflow is undefined, the compiler is permitted to assume it never happens. Under that assumptionvalue + 1 > valueis true for every value the program is allowed to have — so the whole expression folded totrueand the addition disappeared.
Both are legitimate. The standard placed no constraints on a program that overflows a signed integer, so there is nothing to violate.
And the practical response, previewed here and taught later: you do not have to find these by reading. Tools exist that detect undefined behaviour while the program runs. The same program, instrumented:
clang++ -std=c++17 -O0 -fsanitize=undefined ub.cpp -o ub_san && ./ub_san
ub.cpp:6:46: runtime error: signed integer overflow:
2147483647 + 1 cannot be represented in type 'int'A file, a line, a column, and the exact arithmetic that broke. That is the UndefinedBehaviorSanitizer, and Module 25.6 is where you learn to use it as part of a routine. It is mentioned here so you know from the first module that UB is findable rather than mystical.
6. What Your Compiler Adds: Extensions
The third of the three things called C++. Compilers do not implement only the standard — they also add features of their own, and by default many of them accept those extensions silently.
Here is a function that uses one. The array's size is a run-time value:
#include <cstddef>
std::size_t sum_first(const unsigned* src, std::size_t n)
{
unsigned scratch[n]; // not standard C++
std::size_t total = 0;
for (std::size_t i = 0; i < n; ++i) { scratch[i] = src[i]; total += scratch[i]; }
return total;
}A variable-length array is legal in C99. It is not in any version of C++. But:
clang++ -std=gnu++17 -c vla.cpp → accepted, exit 0, no diagnostic
clang++ -std=c++17 -pedantic-errors -c vla.cpp → exit 1
error: variable length arrays in C++ are a Clang extension
[-Wvla-cxx-extension]Two flags, two answers, one source file. The difference is entirely in what you asked for:
-std=gnu++17means "C++17 plus GNU extensions". It is the default for GCC and Clang when no-stdflag is given.-std=c++17means the standard, and-pedantic-errorsmakes a use of an extension an error rather than a silent acceptance.
This is how portable-looking code becomes unportable without anyone noticing. The author compiled with the default, the extension was accepted without comment, and the code builds everywhere that happens to offer the same extension — until it reaches a toolchain that does not, where it fails with an error about a construct the author believed was ordinary C++.
7. How to Make a Claim Attributable
Pulling the chapter together into something you can use in review. When you or a colleague asserts that some C++ behaves a certain way, there are four distinct kinds of claim, and knowing which one is being made settles most arguments:
- "The standard guarantees this." Strongest. Portable across every conforming implementation. Say which version if the feature is newer than C++11.
- "This is implementation-defined, and on our platform it is X." Legitimate and common. Requires naming the platform, and requires a note where the assumption lives so it is findable when the platform changes.
sizeof(long) == 8is this kind of claim. - "This is unspecified; we must not depend on it." The correct response is to restructure the code so the question does not arise — give the two calls in §5 their own statements and the order becomes yours to choose.
- "This happens to work on my compiler." Not a claim about C++ at all. It is a claim about one build of one tool, and it is the category that includes both extensions and undefined behaviour that has not bitten yet.
Most "it works on my machine" disputes dissolve once someone asks which of those four is being asserted.
8. A Note on Simplified Models
One last piece of housekeeping, which is a promise about how this curriculum will talk to you.
Some explanations ahead will be deliberately simplified, because the precise version depends on machinery you have not met. Module 2 will describe the build as four stages, which is what toolchains do rather than what the standard requires. Module 10 will describe "the stack" and "the heap", which are implementation concepts rather than language ones. Module 17.7 will explain virtual dispatch through vtables, which no standard mandates.
Every one of those will be marked as a model when it is introduced, with a pointer to the chapter that makes it precise. A simplification that is identified as one is a teaching tool. A simplification presented as the rule is something the reader has to unlearn later, usually after it has caused a bug — and unlearning is far more expensive than learning carefully the first time.
9. What You Should Be Able to Say Now
- Three different things are called C++: what the ISO standard guarantees, what it deliberately leaves open, and what your compiler adds as extensions.
- C++11 is the dividing line that "modern C++" refers to. C++17 is this curriculum's baseline; C++20 features are labelled wherever they appear.
- C++17 is chosen because it is the newest level you can reasonably assume is fully supported — which matters here because simulator, EDA-plugin and cross-compiler toolchains are often not yours to choose.
__cplusplusreports which standard the compiler is applying: 201703 for C++17, 202002 for C++20 on the machine used here.- Implementation-defined: the implementation chooses and must document.
sizeof(long)is 8 here, 4 on 64-bit Windows. Say the width when the width is part of the specification. - Unspecified: the implementation chooses and need not document or be consistent. Argument evaluation order is unspecified; it printed the same answer three times on this machine, which proves nothing.
- Undefined: no constraints at all, and the program has no meaning.
value + 1 > valueonINT_MAXreturnedfalseat-O0andtrueat-O2from the same source. - UB is not "an unpredictable value". The optimiser is entitled to assume UB never happens and reason from there, which is why effects appear far from causes.
- UB is findable:
-fsanitize=undefinednamed the file, line and arithmetic. Module 25.6 teaches the practice. -std=gnu++17is the standard plus extensions and is often the default.-std=c++17 -Wpedanticis how you find out you have used one.- Reproducible is not guaranteed. Observation tells you what your toolchain did; only the standard tells you what every toolchain must do.
10. Exercises
Exercise 1 — classify each claim. For each, say which of §7's four kinds of claim it is: (a) "a std::uint32_t holds exactly 32 bits"; (b) "sizeof(long) is 8"; (c) "the left operand of + is evaluated first"; (d) "variable-length arrays work fine"; (e) "dereferencing a null pointer crashes".
Exercise 2 — explain the two answers. In your own words, explain why can_increment(INT_MAX) returned false at -O0 and true at -O2, and state which of the two the standard requires. Your answer to the last part should be one word.
Exercise 3 — remove the dependence. Rewrite the call in §5's order.cpp so that the order of the two increments is determined by your code rather than by the compiler. You have not been taught statements formally yet; reason from what the example shows.
Exercise 4 — the dangerous reassurance. A colleague tests a program that contains undefined behaviour, runs it two hundred times, and reports that it always produces the correct answer. Explain what that evidence does and does not establish, and name the one circumstance most likely to change the outcome.
Exercise 5 — find the hidden platform assumption. A model stores a 64-bit timestamp in a long and is built on Linux and on 64-bit Windows. Describe the failure, say which of the four grades of "it depends" it belongs to, and give the one-line fix.
Exercise 6 — justify the flag. Explain why -Wpedantic is the flag that keeps -std=c++17 honest, with reference to §6's two outcomes. Then say what you would expect to happen to a large existing codebase the first time it is compiled with it, and why that is useful information rather than a problem.
11. Summary
- "C++ says X" is incomplete. Separate what the standard guarantees, what it leaves open, and what your compiler added.
- The baseline here is C++17, chosen for toolchain reality rather than taste: simulators, EDA plug-in interfaces and cross-compilers frequently fix the level you may use. C++20 features are labelled.
__cplusplusis the reliable answer to which standard is in force — 201703 for C++17 — and the-stdflag belongs in the build file, not in a default.- Four grades of "it depends." Well-defined; unspecified (implementation chooses, need not say); implementation-defined (chooses, must document); undefined (no constraints, no meaning).
- Implementation-defined is usable once you name the platform.
sizeof(long)was 8 here and is 4 on 64-bit Windows — which is the argument for fixed-width types when a width is part of the specification. - Unspecified is not catastrophic, just not yours to rely on — and this machine's three identical results are exactly why observation is not proof.
- Undefined removes meaning. The same function returned opposite answers at two optimisation levels because the optimiser may assume UB never occurs. Sanitizers find it; Module 25 is the discipline.
- Extensions are accepted by default.
-std=gnu++17compiled a variable-length array silently;-std=c++17 -pedantic-errorsrejected it. Use-std=c++17 -Wall -Wextra -Wpedantic. - Simplified models will be labelled as models, with a pointer to the chapter that makes them precise.
You now have a language with a version, and the vocabulary to say what is guaranteed and what merely happened. The next question is the one every engineer arriving from C asks, and the one that decides whether their C++ is good or merely compiles: given that C++ shares C's memory model and keeps its syntax almost entirely, what actually changed? Chapter 1.4 is that inventory — what carries over, what is genuinely new, and which specific C habits become bugs the moment the file extension changes.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C++ Programming curriculum.
