5.2.32 · D5C++ Programming

Question bank — Modules (C++20) — concept and syntax

1,467 words7 min readBack to topic

This bank builds on the vocabulary from the parent: module unit, primary interface unit, implementation unit, global module fragment, and the export / import gates. Prerequisites worth a glance: The C++ Preprocessor, Translation Units and Linkage, Namespaces in C++, Build Systems and Compilation Speed, and Module Partitions.


True or false — justify

Reveal each line by clicking; the answer side always carries the reason, never a bare verdict.

export module math; may appear anywhere in the interface file as long as it comes before main.
False — it must be the first real declaration; only a global module fragment (module;#includes) and comments may precede it.
A module and a namespace are two names for the same encapsulation mechanism.
False — a namespace groups names for lookup; a module controls which names cross the translation-unit boundary. You can export a namespace, so they compose rather than compete.
import math; textually pastes math's code into your file, just faster.
False — import links a compiled binary interface; nothing is pasted. That is precisely why order and macros no longer leak.
A module can have two files that both begin with export module math;.
False — exactly one primary interface unit is allowed. Extra public files must be partitions like export module math:part;.
module math; (no export) is illegal because every module file needs export.
False — that is a valid implementation unit; it shares the module's internals but adds no new public names.
Marking a class export makes all its members visible to importers.
False — export crosses the module boundary, but private/protected still block member access within the class. The two axes are independent.
Because modules don't leak macros, you can never share a macro between files anymore.
False — a header unit (import "config.h"; or import <cassert>;) does preserve macros; only named-module import suppresses them.
import <iostream>; and #include <iostream> produce identical build behaviour.
False — the header unit is parsed/cached once across the build; the #include re-parses the header text in every translation unit (see Build Systems and Compilation Speed).
Include guards are still required inside a module interface unit.
False — modules are parsed once into a binary interface, so double-definition from re-inclusion cannot happen; guards are pointless there.
export inside an export { … } block means each declaration must also be individually marked export.
False — the block already exports everything inside it; repeating export on each line is redundant, not required.

Spot the error

Each line describes flawed code or reasoning; the reveal names the exact fault and the fix.

export module m; then #include <vector> on the next line — what breaks?
#include is forbidden after the named-module region begins; it belongs in the global module fragment (module; → includes → export module m;).
An implementation file writes export module account; to "match" the interface file — what's wrong?
The implementation unit must be module account; without export. Two export module account; files would declare two primary interface units, which is illegal.
A header puts module; at the very top of a normal, non-module .cpp and expects a global module fragment — problem?
module; only starts a global module fragment when an export module …; follows it in the same file. In a file that never declares a module, it is meaningless/ill-formed.
import math written without the trailing semicolon — why won't it work as a directive?
import is a declaration, not a preprocessor line; like every C++ declaration it needs a terminating ;. Missing it is a syntax error, unlike #include which is line-based.
A caller does import math; and then calls secret_helper() (an un-exported function) — what happens?
Compile error: secret_helper was never exported, so it is invisible across the module boundary even though it exists inside math.
Someone #includes a .ixx interface file instead of importing it — what's the fault?
Textual inclusion drags the module-declaration syntax into the consumer as raw text, breaking compilation. Module interfaces are meant to be imported, not included.
Two partitions export module math:a; and export module math:a; in different files — where's the clash?
Partition names must be unique within a module; two files claiming :a is a redefinition of the same partition interface unit.

Why questions

export and public both mean "outsiders can use it" — why keep two keywords?
They act on different axes: export decides which file-level names leave the module; public decides which class members callers may touch. A private member of an exported class stays hidden.
Why does the standard forbid macros from crossing a named-module import?
To eliminate the silent name/macro leakage that plagues The C++ Preprocessor. Suppressing macros is a deliberate feature that makes an import order-independent and predictable.
Why is a module parsed only once while a header is re-parsed per translation unit?
A module's interface compiles to a binary interface artifact the compiler reuses; a header is plain text the preprocessor re-pastes and re-parses in each translation unit.
Why must export module Name; be the first real declaration?
Everything after it belongs to the named module. If ordinary declarations came first, the compiler couldn't tell whether they are part of the module or the (unnamed) global module — so the boundary must be fixed up front.
Why does the global module fragment exist at all if modules replace #include?
As a quarantine zone: legacy code and third-party headers still use #include, so the fragment lets you pull them in without polluting the module's clean, isolated interface.
Why split a module into a .ixx interface and a .cpp implementation?
The interface advertises what exists; the implementation holds how. Small interface files mean importers recompile rarely, speeding incremental builds.
Why are partitions preferred over just making many interface units?
A module allows exactly one primary interface unit; partitions let you split a large module across files while still presenting a single, coherent public face assembled by the primary unit.

Edge cases

What if a module's primary interface unit exports nothing at all?
Perfectly legal — it defines a module with an empty public surface. Importers gain access to the module name but no usable declarations.
Can the global module fragment contain ordinary code, not just #include?
It is intended only for preprocessor directives (chiefly #include); putting normal definitions there is ill-formed. Real code belongs after export module …;.
Is import allowed to appear inside a function body?
No — import is a module-level declaration and must sit at namespace/global scope, before it is used, not nested inside a function.
What happens to a name that is neither exported nor in an anonymous namespace inside a module?
It has module linkage: visible to other units of the same module but not to importers — a middle ground between fully private and fully public.
Does import std; exist in C++20?
No — import std; is a C++23 feature. In C++20 you use header units such as import <iostream>; to get module-like standard-library imports.
If two importers both import math;, is math compiled twice?
No — the interface is built once into a binary artifact and shared by every importer, which is the core speed advantage over headers.
Can an implementation unit import other modules the interface doesn't?
Yes — an implementation unit may pull in whatever it needs internally without exposing those imports to the module's importers, keeping the public interface minimal.

Recall One-line self-audit before you leave
  • export vs public axis? → boundary-crossing vs class-member access.
  • Only spot for #include in a module? → global module fragment.
  • import std; availability? → C++23, not C++20.
  • Primary interface units per module? → exactly one; the rest are partitions.