10 Modules and Namespaces
10.1 General [module.unit], [namespace.def]
Except as modified by this clause, [module.unit] through [module.private.frag] and [namespace.def] through [namespace.alias] apply unchanged to a SCPP26 program. SCPP26 reuses C++26’s module and namespace syntax verbatim –
export module,module,import,export, andnamespace, including the nested namespace-definition formnamespace A::B::C { ... }– introducing no new keyword, operator, or other token.This clause requires every exported declaration to appear in a namespace determined by its own module’s name (§10.3), gives an import-declaration precise visibility rules, including for a module’s own partitions (§10.4), withholds two namespace forms C++26 otherwise permits (§10.5), and restricts name lookup for a function call whose postfix-expression is an unqualified name (§10.6).
10.2 Module declaration forms and partitions [module.unit]
A translation unit whose first declaration is
export modulemodule-name;is that module’s primary interface unit. A module has exactly one primary interface unit.A module-name may be followed by
:partition-name, naming the translation unit as one partition of that module, distinct from its primary interface unit and from every other partition of the same module:
(2.1) export module
module-name:partition-name;
declares an interface partition, which may itself
export declarations (§10.3);
(2.2) module
module-name:partition-name;
(no leading export) declares an implementation
partition, which shall not export any declaration; an
export-import-declaration (§10.4)
naming such a partition is ill-formed.
- A partition is part of its module but is not itself importable by
name outside that module: only a translation unit that begins with
export modulemodule-name;, or withmodule/export modulemodule-name:other-partition-name;for the same module-name, may contain an import-declaration naming one of that module’s partitions.
[Note: informally, only a file that is itself part of module M – its
primary interface unit, or any one of its partitions – may
import one of M’s other partitions; a file outside M can
only import M as a whole (§10.4).
— end note]
// geometry.scpp -- primary interface unit of module "geometry"
export module geometry;
export import :distance; // re-exports the partition below, see §10.4
namespace geometry {
export int scale(int x) { return x * 10; }
}// geometry_distance.scpp -- an interface partition of the same module
export module geometry:distance;
namespace geometry {
export int manhattan(int ax, int ay, int bx, int by) {
int dx = ax - bx; if (dx < 0) dx = -dx;
int dy = ay - by; if (dy < 0) dy = -dy;
return dx + dy;
}
}// main.scpp -- a translation unit outside module "geometry"
import geometry;
int main() {
return geometry::manhattan(0, 0, 3, 4) + geometry::scale(2); // 27
}10.3 Export declarations and the required namespace [module.interface]
export, applied to a declaration ([module.interface]), marks it visible to a translation unit that imports the module – or, for a declaration of a partition, imports that partition (§10.4). A declaration to whichexportdoes not apply is private to its own module: reachable from elsewhere within the same module (by a qualified or unqualified name, as ordinary lookup permits) but never through an import-declaration.If
exportappertains to a declaration in a translation unit whose first declaration is not one of the forms in 10.2(1) or 10.2(2.1), the program is ill-formed.
[Note: only a primary interface unit or an interface partition may export anything. An implementation partition may not (10.2(2.2)), and a translation unit with no module declaration at all has no module to export from. — end note]
- Let ns(module-name) be the namespace obtained by splitting
a module-name at each
.and treating each resulting identifier as one further level of nesting (so ns(org.lotx.cmath) denotes the namespaceorg::lotx::cmath); for a partition, ns(module-name) is derived the same way from the portion of its module-name preceding the:. Ifexportappertains to a declaration, that declaration shall be a member of ns(module-name) or of a namespace nested, directly or indirectly, inside ns(module-name); otherwise the program is ill-formed.
[Note: (2) and (3) are independent, both-mandatory conditions: a declaration that satisfies (3) but appears in a translation unit that fails (2) is still ill-formed, and vice versa. A namespace that is a proper prefix of ns(module-name), or that diverges from it at any level, does not satisfy (3), even though it may share a leading sequence of identifiers with ns(module-name). — end note]
- A declaration to which
exportdoes not apply is unconstrained by - and may be a member of any namespace, or of none.
export module org.lotx.cmath;
namespace org::lotx::cmath {
export int abs_int(int x) { return x < 0 ? -x : x; } // OK: (3)
namespace detail {
export int clamp(int x, int lo, int hi) { // OK: (3),
return x < lo ? lo : (x > hi ? hi : x); // nested deeper
}
}
}
namespace org::lotx {
int helper() { return 0; } // OK: (4), not exported
export int wrong() { return 1; } // ill-formed: (3), 'org::lotx'
// is a proper prefix, not
// ns(module-name) or deeper
}
export int no_namespace_at_all() { return 2; } // ill-formed: (3)10.4 Import declarations, re-export, and cross-module name merging [module.import]
A plain import-declaration –
importmodule-name;, or, within module M,import :partition-name;for one of M’s own partitions – makes the imported unit’s exported declarations visible in the importing translation unit only; it is not transitive. Importing something that has itself imported (plainly) a third module or partition grants no visibility into that third module or partition.An export-import-declaration –
export importmodule-name;, or, within an interface partition or the primary interface unit of module M,export import :partition-name;– has the same visibility effect as (1) and, in addition, makes every declaration thereby made visible also visible, transitively, to any translation unit that imports the unit containing the export-import-declaration.import :partition-name;, within module M, makes visible – to the importing translation unit only, not transitively – every declaration of that partition, whether or notexportappertains to it.export import :partition-name;is ill-formed if that partition is an implementation partition (10.2(2.2)).
[Note: a plain import of a partition therefore differs
from a plain import of another module by name: the former
exposes all of a partition’s declarations within the same module,
private ones included, while the latter exposes only what that module
itself exports (1). — end note]
- A declaration made visible to a translation unit by one or more import-declarations is merged into that translation unit’s namespaces as if it had been declared, at the same namespace-scope position, in the same translation unit. If two such declarations, or one such declaration and a declaration of the importing translation unit itself, would be ill-formed as a redeclaration or redefinition had both instead appeared literally in one translation unit ([basic.def.odr], [dcl.fct]), the program is ill-formed; otherwise they take part in overload resolution ([over.match]) together, exactly as same-signature and differing-signature declarations of the same name do within a single translation unit.
[Note: there is no dedicated diagnostic for a name made ambiguous by import; a colliding pair of imported declarations is ill-formed for the same reason, and is diagnosed the same way, as a matching pair written directly in one file would be. Two modules may legitimately export declarations of the same qualified name with different signatures – merging into one overload set, by (4) – when their two required namespaces ((3) of §10.3) are related by nesting, for instance because one module’s name is a prefix of the other’s. — end note]
// org.scpp -- primary interface unit of module "org"
export module org;
namespace org::lotx {
export int describe(int x) { return x; }
}// org_lotx.scpp -- primary interface unit of module "org.lotx"
export module org.lotx;
namespace org::lotx {
// OK: (4), a legitimate overload, not a collision
export int describe(int x, int y) { return x + y; }
}// main.scpp
import org;
import org.lotx;
int main() {
return org::lotx::describe(5) + org::lotx::describe(10, 27); // 42
}10.5 Prohibited namespace forms [namespace.unnamed], [namespace.udir]
An unnamed-namespace-definition ([namespace.unnamed]) is ill-formed.
A using-directive ([namespace.udir]) is ill-formed.
[Note: a using-declaration naming a base class member ([namespace.udecl]) in a class’s member-specification is unaffected by (2) and continues to be well-formed (see §11); (2) applies only to a using-directive naming a namespace. — end note]
namespace {
int x = 0; // ill-formed: (1)
}
namespace foo {
int y = 0;
}
using namespace foo; // ill-formed: (2)10.6 Name lookup for unqualified function calls [basic.lookup.unqual], [basic.lookup.argdep]
Argument-dependent lookup ([basic.lookup.argdep]) is not performed for any function call.
Let S be the namespace that most closely encloses the point of a function call whose postfix-expression is an unqualified id-expression naming that function (ignoring, for this purpose, any intervening block, function, class, or lambda scope). Unqualified lookup ([basic.lookup.unqual]) for that id-expression considers only:
(2.1) declarations found in a block, function-parameter, or class scope enclosing the point of the call, exactly as in unmodified C++;
(2.2) declarations that are direct members of S; and
(2.3) declarations that are direct members of the global namespace.
No other namespace is considered, even one that lexically encloses S; in particular, a namespace enclosing S other than the global namespace is never searched, though [basic.lookup.unqual] would otherwise also examine it.
[Note: this restriction applies only to the unqualified lookup
performed for a function call’s own callee-name. Unqualified lookup for
a type, variable, or other non-call use of a name is unaffected and
continues to examine every namespace enclosing the point of use, exactly
as in unmodified C++. A qualified-id, including one beginning
with :: ([basic.lookup.qual]), is likewise unaffected:
N::f() and ::f() are looked up exactly as in
unmodified C++ regardless of (2). — end note]
int global_helper() { return 100; }
namespace outer {
int outer_helper() { return 10; }
namespace inner {
struct Widget { int v; };
int use_it() {
Widget w{}; // OK: unqualified type lookup is
w.v = 1; // unaffected by (2) and climbs to
// 'outer' normally
return global_helper(); // OK: (2.3)
}
int bad() {
return outer_helper(); // ill-formed: (2); 'outer' is
} // neither (2)'s S ('inner') nor
} // the global namespace
}namespace ns {
struct Tag { int v; };
int process(Tag t) { return t.v; }
}
int use(ns::Tag t) {
return process(t); // ill-formed: (1); no argument-dependent
} // lookup, even though 'process' and 'Tag'
// are both members of 'ns'