Compiling evironment: linux (ubuntu 16.04)+ gcc-10.2.

The post will focus on using export,import,visible and reachable.

1 export

C++’s export keyword was originally meant to permit the separation of the definition of a template from its usages. It proved to be incredibly difficult to implement, and the “export templates” C++ feature was dropped in C++11, having never seen widespread usage.

It remained a reserved identifier for several years, and now it returns with renewed purpose and meaning for C++ Modules.

2 The New and Improved (and Completely Repurposed) export

The export keyword may only be used in a module interface unit. The keyword is attached to a declaration of an entity, and causes that declaration (and sometimes the definition) to become visible to module importers.

3 What Can I export?

1) You cannot export entities with internal linkage (static variables and functions; and functions, variables, and classes defined within an anonymous namespace).

namespace {

   // ILLEGAL! This is an anonymous namespace
export void do_stuff() {
// ...
} // ILLEGAL! This is an anonymous namespace
export int five = 5; // ILLEGAL! This is an anonymous namespace
export class stuff {
// ...
}; } // ILLEGAL! This is declared `static`
export static void do_more_stuff() {
// ...
} // ILLEGAL! This is declared `static`
export static int twelve = 12;

2) An exported declaration must actually declare something.

3) The first declaration of an exported entity must be an exported declaration. Subsequent declarations and definitions need not have the export keyword.

export class Thing;  // Good

export class Thing;  // Okay, but redundant

class Thing;  // Implicit `export` keyword

class Thing { // Implicit `export` keyword
int a;
int b;
}; class SomethingElse; // Good. Not exported. export class SomethingElse; // Illegal! First declaration is not exported!

4) Exported declarations may only appear at namespace-scope (this includes the global namespace).

export class Stuff { // Okay
int a;
int b;
}; export class MoreStuff {
int a;
export int b; // lolwut? Illegal.
}; export void foo() {
export int value = get_value(); // wat. No!
} export void bar(export std::string name) { // Please reconsider.
// ...
} template <export typename T> // plz stop
export class my_container {};

5) A using-declaration may be exported unless the referred entity has internal or module linkage.  A using namespace declaration cannot be exported.

namespace Stuff {
export class Widget {}; class Gadget {}; namespace { class Gizmo {}; } // namespace } // namespace Stuff export using Stuff::Widget; // Okay export using Stuff::Gadget; // Not okay
export using Stuff::Gizmo; // Bad
export using namespace Stuff; // Nope

6)  A namespace definition or linkage-specification block may be exported, but all entities declared within the corresponding declaration block must adhere to the above rules.

export namespace foo {

   int eight = 8; // Okay. `eight` is exported as `foo::eight`.

   static int nine = 0; // Illegal!

   namespace {

      void do_stuff() { // Stop! You have violated the law. Pay the court a fine or serve your sentence! Your stolen goods are now forfeit.

      }

   }

}

Note that this rule only applies to the declaration block immediately following the export declaration. This is permissible:

export namespace foo {

   int two = 2; // Okay

}

namespace foo {

   static int six = 6; // Also okay!
// ^ This is not within an exported namespace definition, even though the
// containing namespace `foo` itself is exported by another namespace
// definition }

7) In addition to exporting namespaces definitions and linkage specification blocks, one can also use a bare export block:

export {

   class Aardvark;

   void eat_ants(Aardvark&);

}

Everything within an export block is exported. An export block does not introduce a new scope or change the linkage of its contents. The contents of an export block must follow the same rules as an exported namespace definition (all contained declarations must be exportable).

Aside: A strange (and unfortunate) side-effect of combining rule #6 with rule #2: static_assert may not at the top level of an exported namespace definition. Despite looking like a statement, static_assert is defined as a declaration by the C++ grammar, which permits it to appear in any place that a declaration is valid (otherwise you could not static_assert at namespace scope or within the body of a class definition). Because static_assert does not declare a named entity, exporting a static_assert violates rule #2. Because rule #6 requires all declarations within the namespace definition to be exportable, and static_assert is not exportable, we find that static_assert may not appear within an exported namespace definition. This is a very strange quirk, and to the best of my knowledge there is some effort to add an exception forstatic_assert.

4 Implicit export-ing

There are two important cases where an entity is implicitly exported because of the exported-ness of a separate entity:

1) An exported namespace definition or linkage-specification-block causes every declaration within the immediately following declaration block to be exported.

export namespace Things {

   class Widget { // Implicitly exported as `Things::Widget`

   };

   void foo() { // Implicitly exported as `Things::foo`

   }

}

namespace Items {
export extern "C" {
void do_stuff(); // Implicitly exported as Items::do_stuff
}
export extern "C++" {
void do_other_things(); // Implicitly exported as Items::do_other_things
}
}

2) An exported entity implicitly exports the containing namespace:

namespace baz {
export void quux(); // Exported as baz::quux, and namespace `baz` is now exported
}

Note that having an exported declaration within a namespace definition is not the same as having export on the namespace definition:

namespace europe {

   export class france; // `europe::france` is exported,
// and namespace `europe` is exported. class italy; // Not exported, even though the namespace is exported. namespace { class germany; // Perfectly legal. This is not an
// attempt to export an entity with
// internal linkage. static_assert(true, "Sane"); // Okay. } }

Read this as: europe is an exported namespace, and its namespace definition contains exported entities, but the above is not an exported namespace definition.

5 export import?

In the last post, we saw export import used with module partitions:

export module my_module;
// Add `some_partition` to our module interface:
export import :some_partition;

But export import may also appear on regular imports:

export module my_module;
export import widgets_inc;

The result is that users who import my_module will “implicitly” import widgets_inc. This relationship is fully transitive.

6 Rules Regarding import

export has been a reserved word for many years, but now we have a new contextual keyword: import. We’ve seen it quite a bit, and it is mostly self-explanatory.
However, just like with export, there are some rules about using import:

1) In a module unit, all imports must appear before any declarations in that module unit. You cannot import at arbitrary points in a module unit.

export module yo;

import dogs;

void pet(dog& d);

import cats; // Not allowed! Move this import above `pet`

import is a special identifier, and it is still possible to use import as a name for other entities:

export module yo;

import dogs;

class import {};

import i1; // Illegal! `import` declarations must appear in the preamble
::import i2; // Okay. Declares a variable `i2` of type `import`. class Widget {
import member; // Okay. No scope-resolution needed.
};

In a non-module translation unit, import may appear after declarations in the translation unit.

2) import declarations may only appear at global scope.

3) An import that names a module partition may only name partitions that belong to the same module which contains the import.

4) A module may not import itself.

5) A module unit A may not have an interface dependency on itself (a cyclic import) (see below regarding “interface dependencies).

Even though you are still able to use import as an identifier, please don’t, for the sake of our (and your own) sanity.

(As far as this author can determine, a namespace-scope declaration of unqualified type import or function returning unqualified type import is the only instance of C++ Modules breaking existing code.)

7 The Implicit import

There is one scenario in which an import implicitly occurs:

// europe.cpp
export module europe; struct Country {};
struct France;
struct Germany;
// europe_impl.cpp
module europe; struct France : Country {};
struct Germany : Country {};

If you recognize this from the prior post, I referred to such module units as europe_impl.cpp as anonymous implementation units. This name isn’t a name provided by the standard document, but I find it a useful shorthand when talking about “module implementation units that are not partitions.”

You’ll notice above that europe_impl.cpp refers to struct Country in the definitions of France and Germany, but it never imports anything!
“Anonymous” module implementation units are defined to have an “implicit” import of the module in which they belong. In fact: Adding an import europe is not allowed (and, because of the implicit import, not necessary).
You might think this could create a cyclic import, but it cannot: There is no way for any other module unit to import an implementation unit with no partition name. Its contents are unreachable from any other translation unit. Even the rest of europe cannot get at the contents of this file. Only within europe_impl.cpp will France and Germany be complete types (they will be seen as incomplete types everywhere else).

8 Interface Dependencies

This aspect is best explained by simply pasting the relevant text from the specification:

translation unit has an interface dependency on a module unit U if it contains a module-declaration or module-import-declaration that imports U or if it has an interface dependency on a module unit that has an interface dependency on U.

Suppose the following program:

// a.cpp
export module Foo;
// b.cpp
export module Bar; import Foo;
// c.cpp
import Bar;

c.cpp has an interface dependency on b.cpp by virtue of importing the module of which that module unit is a member. Even though it does not name a.cpp or the module thereof, it does have an interface dependency on a.cpp because a.cpp is an interface dependency of b.cpp. Interface dependencies are transitive.

9 The Dungeon Boss: visible versus reachable

C++ Modules introduces two new concepts: visibility and reachability, which help distinguish between what code is semantically valid in the face of (possibly implicitly) imported constructs.

What is Visibility?

Let’s look at the simplest visibility rule of all:

// main.cpp

#include <iostream>

const char* get_phrase() {
return "Hello, world!";
} int main() {
std::cout << get_phrase() << '\n';
}

We’re not even using any module features here, but the rules of visibility and reachability have an effect on all code. In this sample, get_phrase is (obviously) “visible” from main.

A name is visible at a point within a translation unit if that name has been declared at an earlier point within the same translation unit. This is unchanged from C++17: Namespace-scope declarations are still not “hoisted”!

There are other situations in which visibility comes into play:

export module speech;

export const char* get_phrase() {
return "Hello, world!";
}
// main.cpp
import speech; import <iostream>; int main() {
std::cout << get_phrase() << '\n';
}

In this sample, get_phrase is visible within main.cpp because of the import speech; declaration.

“Visible” means “a candidate for name lookup.” Visibility applies to more than just namespace-scope items:

export module speech;

export struct Phrase {
const char* spelling = nullptr;
}; export Phrase get_phrase() {
return Phrase{"Hello, world!"};
}
// main.cpp
import speech; import <iostream>; int main() {
Phrase phr = get_phrase();
std::cout << phr.spelling << '\n';
}

In this sample, Phrase::spelling is also visible, which allows us to ask for the member when we have an instance of that object.

A Trickier Beast: Reachability

C++ Modules introduce a new concept of reachability. When an entity is reachable, the semantic properties of that entity are available, but not necessarily visible to name lookup. It is essential to understand the following:

1) Every visible entity is also reachable.

2) Being reachable does not imply being visible.

3) Whether an entity is declared with export has no effect on if it is reachable: It only effects whether it is visible.

4) If a class or enumeration type is reachable, then its members become visible, even if the containing name is not visible.

Point 4 has some interesting implications: We can have access to the semantic properties and member names of a class even if we cannot name that class.

This allows for some crazy 1337 h4cks:

export module Secrets;

// NOT EXPORTED!
class SecretClass {
public:
explicit SecretClass(int i) : value(i) {}
SecretClass(const SecretClass&) = delete;
SecretClass(SecretClass&&) = default; int value = 0;
}; // Export a function that returns our non-exported class type
export SecretClass get_secret() {
return SecretClass{42};
}
import Secrets;

void foo() {
// ILLEGAL: `SecretClass` is not visible:
SecretClass s1 = get_secret(); // Okay: `SecretClass`'s move-constructor is *reachable*:
auto s2 = get_secret(); // Okay: The members of the class are *visible*
int secret_value = s2.value; // ILLEGAL: `SecretClass` is not copyable:
auto s3 = s2; // Okay: A move-construction of `SecretClass`:
auto s4 = std::move(s2); // WHOA: Okay: Grab the class and give it a name.
using NamedClass = decltype(s2); // Okay: The constructor of `SecretClass` is reachable, and we've now got
// a name on the class.
NamedClass s5{53};
}

You might believe that this is wildly unprecedented, but we’ve had a similar concept of “usable but not visible” since C++14, no modules required:

auto foo() {
struct Money {
int amount;
};
return Money{ 12 };
} int bar() {
auto f = foo();
using SecretType = decltype(f);
SecretType my_money{34};
return my_money.amount; // Returns 34!
}

The Easy Case: Necessary Reachability

The standard specifies a sub-category of reachability called necessary reachability. Being necessarily reachable is an attribute of a translation unit and propagates to the entities declared within it.

The rules of necessary reachability are simple: A translation unit is necessarily reachable if and only if it is a module interface unit on which the requesting translation unit has an interface dependency.

// eurasia_base.cpp
export module eurasia:base; import <string>; struct Country {
std::string common_lang;
};
// eurasia_west.cpp
export module eurasia:west; import :base; struct Spain : Country {
Spain() : Country{"es"} {}
};
struct France : Country {
France() : Country{"fr"} {}
};
// eurasia_east.cpp
export module eurasia:east; import :base; struct Japan : Country {
Japan() : Country{"jp"} {}
};
struct Russia : Country {
Russia() : Country{"ru"} {}
};
// eurasia.cpp
export module eurasia; import <memory>; import export :base;
import export :east;
import export :west; export const Country& get_country() {
// ...
}
// main.cpp
import eurasia; import <iostream>; int main() {
auto& c = get_country();
std::cout << "Country language is " << c.common_lang << '\n';
}

Even though Country is not exported (nor visible), c.common_lang is valid for the following reasons:

1) Every module unit in eurasia is an interface dependency of main.cpp.

2) All partitions in eurasia are interface partitions.

3) Therefore: every module unit (and the things declared within) are necessarily reachable from main.cpp.

4) By being reachable, the members of Country become visible to main.cpp.

The name of “necessary” reachability is to convey the idea that these rules enforce a baseline behavior for the propagation of semantic properties between translation units in a modularized program.

An astute observer will notice that the partitions :base, :east, and :west do not actually export anything, despite themselves being interface partitions (with the export module declaration).

You might therefore assume it safe to simply remove the export keyword from the module declaration, right?

Not so fast!

Remember the rule of necessary reachability:

A translation unit is necessarily reachable if and only if it is a module interface unit on which the requesting translation unit has an interface dependency.

Note that this only applies to interface module units. If we remove the export keyword from the module declaration, the module partition becomes an implementation partition.

The standard goes on to say this:

It is unspecified whether additional translation units on which the […] [translation unit] has an interface dependency are considered reachable, and under what circumstances.

It also features two non-normative notes:

Implementations are therefore not required to prevent the semantic effects of additional translation units involved in the compilation from being observed.

It is advisable to avoid depending on the reachability of any additional translation units in programs intending to be portable.

This means that removing the export keyword in the declaration export module eurasia:base; might or might not break main.cpp!

We haven’t much deployment experience to say whether this caveat will be extremely relevant, and it only manifests in an already-fairly-contorted program.

Nevertheless: Heed this warning: Do not depend on arbitrary things being reachable beyond what is necessarily reachable.

Understanding C++ Modules In C++20 (2)的更多相关文章

  1. Understanding C++ Modules In C++20 (1)

    Compiling evironment: linux (ubuntu 16.04)+ gcc-10.2. The Post will clarify and discuss what modules ...

  2. C++20 的 Modules

    最近看了两篇关于 C++ 20 Modules 很有意思的文章,戳: <Understanding C++ Modules: Part 1: Hello Modules, and Module ...

  3. ODOO-10.0 错误 Could not execute command 'lessc'

    2017-01-05 20:24:12,473 4652 INFO None odoo.service.db: Create database `hello`. 2017-01-05 20:24:16 ...

  4. Cheatsheet: 2015 12.01 ~ 12.31

    Mobile Setting Up the Development Environment iOS From Scratch With Swift: How to Test an iOS Applic ...

  5. ubuntu下简单的驱动编译

    转自:http://www.eefocus.com/jefby1990/blog/13-02/291628_c39b8.html 本文是参考了网上多篇帖子而写的算不上什么原创.唯一值得欣慰的只不过在本 ...

  6. Linux内核树的建立-基于ubuntu系统

    刚看 O'REILLY 写的<LINUX 设备驱动程序>时.作者一再强调在编写驱动程序时必须 建立内核树.先前的内核只需要有一套内核头文件就够了,但因为2.6的内核模块吆喝内核源码树中的目 ...

  7. vTPM环境部署(ubuntu)

    注:1.系统:ubuntu16.04LTS2.ISO镜像:/home/huanghaoxiang/ubuntu-server.iso3.IMG路径:/home/TPM-Machine4.Login: ...

  8. 如何开发由Create-React-App 引导的应用(二)

    此文章是翻译How to develop apps bootstrapped with Create React App 官方文档 系列文章 如何开发由Create-React-App 引导的应用 如 ...

  9. dfsdf

    This project was bootstrapped with Create React App. Below you will find some information on how to ...

随机推荐

  1. 1109 01组成的N的倍数

    1109 01组成的N的倍数 基准时间限制:1 秒 空间限制:131072 KB  给定一个自然数N,找出一个M,使得M > 0且M是N的倍数,并且M的10进制表示只包含0或1.求最小的M.   ...

  2. Java学到什么程度可以面试工作?

    ​先说结论: 1 大多数公司,对于Java初级开发的要求是,会用Spring Boot+JPA做增删改查 2 所以零基础的Java小白,无需学太多的内容,只要掌握Spring Boot+JPA做增删改 ...

  3. Redis 的 3 种集群方案对比

    数据持久化 主从复制 自动故障恢复 集群化 数据持久化本质上是为了做数据备份,有了数据持久化,当Redis宕机时,我们可以把数据从磁盘上恢复回来,但在数据恢复之前,服务是不可用的,而且数据恢复的时间取 ...

  4. 【C++】关键字回忆leetcode题解

    20200515 前缀和 no.560 20200518 动态规划 no.152 20200520 状态压缩 no.1371 20200521 中心扩散 no.5 20200523 滑动窗口 no.7 ...

  5. Kernel PCA and De-Noisingin Feature Spaces

    目录 引 主要内容 Kernel PCA and De-Noisingin Feature Spaces 引 kernel PCA通过\(k(x,y)\)隐式地将样本由输入空间映射到高维空间\(F\) ...

  6. Simplicial principal component analysis for density functions in Bayes spaces

    目录 问题 上的PCA Hron K, Menafoglio A, Templ M, et al. Simplicial principal component analysis for densit ...

  7. Android 常见对话框的简单使用(提示信息对话框、单选多选对话框、自定义对话框)

    目录 一.提示信息对话框: 二.单选对话框: 三.多选对话框: 四.自定义对话框: 演示项目完整代码: 一.提示信息对话框: //显示提示消息对话框 private void showMsgDialo ...

  8. Nginx_安装配置

    一.安装gcc依赖库 检查是否安装(linux默认是安装了的) gcc –version  

  9. nginx配置图片路径

    首先, 在linux下创建你存放资源的目录,例如:/data/images:用于存放图片. 下一步,打开default.conf配置文件找到server块下的location添加如下 location ...

  10. 在CentOS7上安装 jq

    安装EPEL源: yum install epel-release 安装完EPEL源后,可以查看下jq包是否存在: yum list jq 安装jq: yum -y install jq 命令参考资料 ...