Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

15 Nov 2020

C++20 模块规范阅读笔记【一】

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
}

(本篇完)

comments powered by Disqus