C++ Modules规范阅读笔记。
10.1 Module units and purviews
一个俱名模块(named module)是一系列使用同一名字的模块单元的合集。
一个模块单元(module unit)是一个带有模块声明的翻译单元(translation unit)。模块声明形式如下:
module-declaration:
export-keyword_opt module-keyword module-name module-partition_opt attribute-specifier-seqopt ;
模块接口单元(module interface unit)是这样一个模块单元,它的模块声明是以export关键字开头的。除了接口单元以外,其他的模块单元都算作模块实现单元。一个俱名模块只能包含一个接口单元,并且这个接口单元不能包含模块片区。这个接口单元叫做主接口单元。
模块片区(module partition)是这样一种模块单元,其模块声明中包含模块片区的定义。一个俱名模块中包含的模块片区必须具有不同的片区定义。如果一个模块片区是一个模块接口单元,那么这些接口单元中的定义必须由主接口单元导出。也就是说,模块片区只能在俱名模块内部可见,对外部则不可见。
下面是一些例子:
翻译单元1:一个主模块接口单元
export module A;
export import :Foo;
export int baz();
翻译单元2:模块片区A:Foo(其符号通过主接口单元导出)
export module A:Foo;
import :Internals;
export int foo() { return 2 * (bar() + 1); }
翻译单元3:模块片区A:internals(其符号未通过主接口单元导出)
module A:Internals;
int bar();
翻译单元4:一个模块实现单元
module A;
import :Internals;
int bar() { return baz() - 10; }
int baz() { return 30; }
模块单元的覆盖范围(module unit purview)从模块定义开始,到文件结束为止。对于俱名模块M,它的purview则是所有从属模块单元purview的合集。
全局模块没有名字,但是包含全局模块分段(global module fragments)的合集,以及所有不属于模块单元的翻译单元的合集。
一个模块要么是俱名模块,要么是全局模块。一个声明如果在一个模块的范围内出现,一般是属于这个模块的,除了几种例外情况。
一个模块声明如果既不包含export关键字,也不包含模块片区,则默认导入模块主接口单元,就像是隐式使用了模块导入声明一样:
module-import-declaration:
import-keyword module-name attribute-specifier-seqopt ;
import-keyword module-partition attribute-specifier-seqopt ;
import-keyword header-name attribute-specifier-seqopt ;
示例二。
编译单元1:
module B:Y; // does not implicitly import B
int y();
编译单元2:
export module B;
import :Y; // OK, does not create interface dependency cycle
int n = y();
编译单元3:
module B:X1; // does not implicitly import B
int &a = n; // error: n not visible here
编译单元4:
module B:X2; // does not implicitly import B
import B;
int &b = n; // OK
编译单元5:
module B; // implicitly imports B
int &c = n; // OK
10.2 Export declaration
导出声明的语法如下:
export-declaration:
export declaration
export { declaration-seqopt }
export-keyword module-import-declaration
一个导出声明须出现在namespace范围内以及在模块的purview之内。不可出现在匿名的命名空间或者一个私有模块片段(private-module-fragment)之内。一个导出声明并不构成一个范围,所以其作用的declaration以及declaration-seqopt中不可再包含其他的导出声明,或者模块导入声明(module-import-declartion)。
下面是一个例子:
假设有a.h,内容如下:
export int x;
然后有翻译单元#1,内容如下:
module;
#include "a.h" // error: declaration of x is not in the
// purview of a module interface unit
export module M;
export namespace {} // error: does not introduce any names
export namespace {
int a1; // error: export of name with internal linkage
}
namespace {
export int a2; // error: export of name with internal linkage
}
export static int b; // error: b explicitly declared static
export int f(); // OK
export namespace N { } // OK
export using namespace N; // error: does not declare a name
如果导出的声明是一个using声明,并且这个声明不在一个头部单元,那么using声明涉及的名字必须具有外部链接属性(external linkage)。
下面是另一个例子:
源代码b.h:
int f();
源代码c.h:
int g();
翻译单元#1:
export module X;
export int h();
翻译单元#2:
module;
#include "b.h"
export module M;
import "c.h";
import X;
export using ::f, ::g, ::h; // OK
struct S;
export using ::S; // error: S has module linkage
namespace N {
export int h();
static int h(int); // #1
}
export using N::h; // error: #1 has internal linkage
上面的规则对typedef以及alias-declaration不适用,下面的例子是成立的:
struct S;
export using T = S; // OK, exports name T denoting type S
对已导出声明的再声明是可以的,但是再导出声明一个未导出的声明是一种病构:
export module M;
struct S { int n; };
typedef S S;
export typedef S S; // OK, does not redeclare an entity
export struct S; // error: exported declaration follows non-exported declaration
一个名字,不管是在导出声明中引入,还是在导出声明中引用,只要这个导出声明处于模块的覆盖范围,那么这个名字就会被导出。
导出的名字要么是外部链接,要么是不可链接。命名空间范围的名字导出后,被某个模块导入,那么在所导入模块的名字查找中是有效的。类和枚举成员名字在此类型可触达的范围内可见于名字查找。
下面是例子。
M的接口单元:
export module M;
export struct X {
static void f();
struct Y { };
};
namespace {
struct S { };
}
export void f(S); // OK
struct T { };
export T id(T); // OK
export struct A; // A exported as incomplete
export auto rootFinder(double a) {
return [=](double x) { return (x + a/x)/2; };
}
export const int n = 5; // OK, n has external linkage
M的实现单元:
module M;
struct A {
int value;
};
main程序:
import M;
int main() {
X::f(); // OK, X is exported and definition of X is reachable
X::Y y; // OK, X::Y is exported as a complete type
auto f = rootFinder(2); // OK
return A{45}.value; // error: A is incomplete
}
使用导出声明导出一个名字的时候,不能改变该名字的链接性质:
export module M;
static int f(); // #1
export int f(); // error: #1 gives internal linkage
struct S; // #2
export struct S; // error: #2 gives module linkage
namespace {
namespace N {
extern int x; // #3
}
}
export int N::x; // error: #3 gives internal linkage
如果导出声明的目标是一个命名空间或者具有规定的链接性质,那么该目标所包含的声明也受导出规则的限制:
export module M;
export namespace N {
int x; // OK
static_assert(1 == 1); // error: does not declare a name
}
(本篇完)