Introduction
Modules TS is on the way to being approved. A proposal on Modules is available in [1]. We hope it will be in the C++20 Standard.
Issues that modules solve:
(1) Reduction of compile time. Insure that the code it compiled once. At present in C++, header files (especially those containing templates) take a lot of time to compile, and they may be compiled several times if called in various source files.
(2) Prevention of interdependency between various items of code. Usually #include directives often have macros that may influence each other. The order of import declarations for modules is irrelevant as long as there is no name clash. Modules do not export macros, which ensures that there is no collision between macro names.
(3) Avoidance of confusion between header files that exists when using libraries. In C, libraries and header files are stored separately, which makes it easy to confuse which header file belongs to a given library. The import declaration for module will automatically access the right interface.
Name clashes in issue (2) can be prevented by using different namespaces for the exported objects: for example, it is possible for a namespace to have the same name as the name of the module it belongs to.
Module Declarations
The general structure of a program with modules is shown in Figure 1.
Figure 1. General Program Structure with Modules
Let's consider all the details. Each rectangle represents a translation unit, which is stored in a file and is compiled separately. Each module (I mean "named module", which has a name) can be composed of one interface unit and zero or more implementation units. An interface unit contains the following declaration:
export module module_name;
The item module_name, which consists of one or more identifiers separated by dots, is the name of the module to be exported. Each implementation unit (if available) should contain the following declaration:
module module_name;
Each translation unit should have no more than one module declaration ("export module module_name;" or "module module_name;"). A module declaration does not have to be the first declaration in a module. The declarations that occur before a module declaration belong to the global module that does not have any name. This allows to use various includes that can be shared between the modules, but will not be exported by the named module.
Each module usually has to export some entities: classes, variables, constants, functions, templates, etc. Those entities should be exported, which means that their declarations should be preceded by an "export" keyword.
If it is necessary to import a module, in order to use the entities exported by it, the following construct is used:
import module_name;
It is possible to import a module and export all of its contents into another module. In this case the following declatation can be used:
export import module_name;
In terms of compilation: each module is compiled once. Modules may be interdependent. But there cannot be any circular dependency between their interfaces. That means that the interfaces should be ordered: an interface that imports a module should be compiled after the interface that exports this module. The implementations should be compiled after the interfaces.
A Simple Example
Let's look and a simple example with one module S1
, which is defined using only an interface unit:
export module S1;
export int n = 25;
export constexpr double p = 3.2;
export struct A
{
void print() const;
};
void A::print() const
{
std::cout << "A. n:" << n << " p:" << p << std::endl;
n = 22;
}<span style="display: none;"> </span>
The program file Test_S1.cpp contains the following code:
import S1;
int main()
{
A x;
std::cout << "n:" << n << " p:" << p << std::endl;
n= 27;
x.print();
std::cout << "n:" << n << " p:" << p << std::endl;
}
The program will print:
n:25 p:3.2
A. n:27 p:3.2
n:22 p:3.2
The standard library imports haven't been fully fixed yet. I am giving an example based on the Visual C++ 2017 implementation.
This example shows that a module can be easily defined using only an interface unit. As you see, the export declarations should follow the module declaration. If an entity is not exported it won't be imported.
It is possible to split the module definition between two units -- an interface and an implementation -- as follows:
export module S1;
export int n = 25;
export constexpr double p = 3.2;
export struct A
{
void print();
};
import std.core;
module S1;
void A::print()
{
std::cout << "A. n:" << n << " p:" << p << std::endl;
n = 22;
}
But this does not make much sense here, although it will reduce the size of the interface file. The Test_S1.cpp will stay the same.
Mutual Dependency Between Modules
It is necessary to split modules between an interface and implementation only if there is a mutual dependency between modules. The typical example is mutually recursive functions. Consider the following two mutually-recursive integer functions.
Function f:
f(0) = 1
f(n) = 1+q(n-1), if n > 0
Function q:
q(0) = 0
q(n) = q(n-1)+n*f(n-1), if n > 0
In this case the interface units can be defined as follows:
export module F1;
export int f(int n);
export module Q1;
export int q(int n);
The interfaces are present. In this case, in the implementation units we can import these modules:
import Q1;
module F1;
int f(int n)
{
if (n == 0)
return 1;
return 1+q(n-1);
}
import F1;
module Q1;
int q(int n)
{
if (n == 0)
return 1;
return q(n-1)+n*f(n-1);
}
The program file F1Q1_Test.cpp can be as follows:
import std.core;
import Q1;
import F1;
int main()
{
for (int n = 0; n <= 10; n++)
{
std::cout << "n= " << n << " f(n)= " << f(n) << " q(n)= " << q(n) << std::endl;
}
}
The output of this program will be:
n= 0 f(n)= 1 q(n)= 1
n= 1 f(n)= 2 q(n)= 2
n= 2 f(n)= 3 q(n)= 6
n= 3 f(n)= 7 q(n)= 15
n= 4 f(n)= 16 q(n)= 43
n= 5 f(n)= 44 q(n)= 123
n= 6 f(n)= 124 q(n)= 387
n= 7 f(n)= 388 q(n)= 1255
n= 8 f(n)= 1256 q(n)= 4359
n= 9 f(n)= 4360 q(n)= 15663
n= 10 f(n)= 15664 q(n)= 59263
A Bit More Complicated Example: Importing and Exporting
Here is an example, which I found on the internet [2]. I have slightly modified it to use the right syntax of Modules TS, and added the pets.cat module.
We start with the base class:
import std.core;
export module pets.pet;
export class Pet
{
public:
virtual std::string says() = 0;
};
Two derived classes Cat
and Dog
are defined in separate modules:
import std.core;
export module pets.cat;
import pets.pet;
export class Cat : public Pet
{
public:
std::string says() override;
};
std::string Cat::says()
{
return "Miaow";
}
import std.core;
export module pets.dog;
import pets.pet;
export class Dog : public Pet
{
public:
std::string says() override;
};
std::string Dog::says()
{
return "Woof!";
}
Now we can combine all this definitions in one module, so that only one module can be imported instead of three:
export module pets;
export import pets.pet;
export import pets.dog;
export import pets.cat;
Now the main module can import the module pets
and use all the three modules:
import pets;
import std.core;
import std.memory;
int main()
{
std::unique_ptr<Pet> pet1 = std::make_unique<Dog>();
std::cout << "Pet1 says: "
<< pet1->says() << std::endl;
std::unique_ptr<Pet> pet2 = std::make_unique<Cat>();
std::cout << "Pet2 says: "
<< pet2->says() << std::endl;
}
The output of the program will be:
Pet1 says: Woof!
Pet2 says: Miaow
Avoiding Name Clashes: Using Namespaces
In general, modules will not eliminate problems with name classes. There will be a problem, when two modules are imported that have objects with the same names. This can be avoided if the exported objects are included into a namespace that has the same name as the module. Here is an example, based on the module S1
that I showed before:
import std.core;
export module S2;
export namespace S2
{
int n = 25;
constexpr double p = 3.2;
struct A
{
void print() const;
};
void A::print() const
{
std::cout << "A. n:" << n << " p:" << p << std::endl;
n = 22;
}
}
The main module can look as follows:
import std.core;
import S2;
int main()
{
S2::A x;
std::cout << "n:" << S2::n << " p:" << S2::p << std::endl;
S2::n= 27;
x.print();
std::cout << "n:" << S2::n << " p:" << S2::p << std::endl;
}
If output of this program will be the same as before with module S1
:
n:25 p:3.2
A. n:27 p:3.2
n:22 p:3.2
Running Examples in CppModuleBuilder
I have created a simple IDE environment, that compiles and builds the modules using Visual C++ 2017 command line. It is easier to try modules in an IDE, which builds the whole program automatically out of the available modules, than to execute several command lines, take care of their order. This tool is designed for demonstartion only and is not intended for serious development.
The IDE is written in C# and looks like this:
The icons on the toolbar are self-explanatory: New File, Load File, Save File, Find, Replace, Remove File, Load Project, Save Project, Close Project, Build, Run. When a new file is created the user must give it a name immediately. The name of the files should correspond the name of the modules used. The interface modules should have the extension .ixx; whereas the implementation modules, the extension .cxx. The main module should have extension .cpp. There can be no more than one implementation for each module. The whole structure of the program is shown in Figure 2.
Figure 2. The structure of the program in CppModuleBuilder
A project can be loaded by clicking on the Load Project button on the Toolbar and selecting a file with the extension .proj. There are soveral sample projects that you may use. The first time you use the build a dialog box will appear, as shown in Figure 3.
Figure 3. Select Developer Command Prompt Dialog Box
In this case, you must select the VsDevCmd.bat path, which corresponds to the latest Visual Studio that you would like to use. In this example, it will be the first line.
When starting a new project, you may close the previous one, create new files and save them as a project. You must always give extension .proj for project files.
Any extra information about C++ Modules in Visual Studio in available here [3,4].
CppModuleBuilder processes the files and copies the processed files into the cpp_module_builder directory in the user's "My Directory" folder. There they are compiled.
I have also introduced some "tweaks" -- extra preprocessing of the source code:
(1) The "export module" is actually is not available yet. VC++ uses "export". CppModuleBuilder remove "export".
(2) If both the interface and the implementation of a module are present and a variable is exported, the compilation failes if the variable is not defined in the implementation file as well. I had to put it into an .hxx file, which is imported at the beginning of the implementation file.
All these tweaks will not be necessary in the future, when the modules are properly implemented.
Other Issues: Mutual Dependency of Classes
Consider the following example:
export module P1;
struct SP2;
export struct SP1
{
SP1(int m):v1(m) {};
SP2* p2;
void print();
int v1;
};
export struct SP2
{
SP2(int m):v2(m) {}
SP1* p1;
void print();
int v2;
};
import std.core;
module P1;
void SP1::print()
{
std::cout << "SP1 self:" << v1<< " the other:" << p2->v2 << std::endl;
}
void SP2::print()
{
std::cout << "SP2 self:" << v2<< " the other:" << p1->v1 << std::endl;
}
The main module may look like this:
import P1;
int main()
{
SP1* s1 = new SP1(10);
SP2* s2 = new SP2(5);
s1->p2 = s2;
s2->p1 = s1;
s1->print();
s2->print();
delete s1;
delete s2;
};
This program can be compiled and run in the VC++2017. You may use CppModuleBuilder. But what if we want to split the module P1 into two modules, so that each class will be in a separate module. The problem here is that we have to reference the other module in the interface. We cannot import them: circular references are not allowed. Modules can be compiler in a sequence, but each module can be compiled only once.
How can we possibly reference an object in another module without importing the module? The so-called proclaimed ownership declaration helps. It reminds me function forwarding. We don't fully declare an object: just reference it, saying that it will be declared later. It's good enough to define a pointer to it. The proclaimed ownership declaration has the following syntax:
extern module module_name: declaration;
Here is how we can split the interface between the modules.
export module SP1M;
extern module SP2M: struct SP2;
export struct SP1
{
SP1(int m):v1(m) {};
SP2* p2;
void print();
int v1;
};
export module SP2M;
import SP1M;
export struct SP2
{
SP2(int m):v2(m) {}
SP1* p1;
void print();
int v2;
};
I deliberately did not give the names of the files because the implementation is not available yet. You cannot try this example.
Further Information
You may wish to look at Clang [5] and download Clang 5.0.0 [6]. I have tried it in Windows.
References
- www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4689.pdf
- https://schneide.wordpress.com/2017/07/09/c-modules-example/
- https://blogs.msdn.microsoft.com/vcblog/2017/05/05/cpp-modules-in-visual-studio-2017/
- https://blogs.msdn.microsoft.com/vcblog/2015/12/03/c-modules-in-vs-2015-update-1/
- https://clang.llvm.org/docs/ReleaseNotes.html
- http://releases.llvm.org/download.html