C++20 is right around the corner. Along with the new standard comes the much anticipated Modules feature! The compiler team initially announced that we were working on the Modules TS back in 2017 and since then we have been hard at work improving the feature and improving compiler conformance around this feature. We finally feel it is time to share some of the progress we have made on the conformance front for Modules.
What’s new?
- Header units are a new form of translation unit which act like portable PCHs.
- Context sensitive
module
andimport
keywords provide users with more flexibility when using these terms as identifiers in code. - Global module fragment is a way of separating non-modular code from module interface code when composing a module interface.
- Module partitions are a type of module interface which compose a larger module interface.
- IntelliSense status as of Visual Studio 2019 version 16.6 Preview 2.
Header Unit Support
In C++20 [module.import]/5 describes the import of a new translation unit type, the header unit. The semantics of this type of import are further elaborated on in [module.import]/5 and one of the more important pieces of information is that macros defined in that imported header are also imported:
myheader.h
#pragma once #include <cstdio> #define THE_ANSWER 42 #define STRINGIFY(a) #a #define GLUE(a, b) a ## b
main.cpp
import "myheader.h"; int f() { return THE_ANSWER; } int main() { const char* GLUE(hello_,world) = STRINGIFY(Hello world); std::printf("%s\n", hello_world); }
The sample above can be compiled using the new /module:exportHeader
switch:
$ cl /std:c++latest /W4 /experimental:module /module:exportHeader myheader.h /Fomyheader.h.obj
$ cl /std:c++latest /W4 /experimental:module /module:reference myheader.h:myheader.h.ifc main.cpp myheader.h.obj
Notice the use of /module:exportHeader
, the argument to this option is a path (relative or absolute) to some header file. The output of /module:exportHeader
is of our .ifc format. Meanwhile, on the import side, the option /module:reference
has a new argument form which is <path-to-header>:<path-to-ifc>
and either one or both of the paths expressed in the argument to /module:reference
can be relative or absolute. It is also important to point out that without the /Fo
switch the compiler will not generate an object file automatically, the compiler will only generate the .ifc.
One other intended use case of the /module:exportHeader
is for users (or build systems) to provide a text argument to it which represents some header name as the compiler would see. A quick example is:
$ cl /std:c++latest /EHsc /experimental:module /module:exportHeader "<vector>" /module:showResolvedHeader
<vector>
Note: resolved <vector> to 'C:\<path-to-vector>\inc\vector'
This use of /module:exportHeader
enables the compiler to build header units using the header search mechanism as if the argument were written in source. This functionality also comes with a helper switch, /module:showResolvedHeader
, to emit the absolute path to the header file found through lookup.
Note to readers: there is a known limitation with /module:exportHeader
and its interaction with /experimental:preprocessor
these two switches are currently incompatible and will be resolved in a future release.
Context Sensitive module
and import
keywords
In the Modules TS both module
and import
were treated as keywords. It has since been realized that both of these terms are commonly used as identifiers for user code and hence a number of proposals were accepted into C++20 which add more restrictions as to when module
and import
are keywords. One such proposal was P1703R1 which adds context sensitivity to the import
identifier. Another such proposal—but one which is not yet accepted—is P1857R1. P1857R1 is interesting in that it is the most restrictive paper in defining when module
and import
are keywords or identifiers.
As of 16.5 MSVC will implement both P1703R1 and P1857R1. The result of implementing the rules outlined in these two papers is that code such as:
#define MODULE module #define IMPORT import export MODULE m; IMPORT :partition; IMPORT <vector>;
Is no longer valid and the compiler will treat the macro expansion of both MODULE
and IMPORT
as identifiers, not keywords. For more cases like this please see the papers, in particular P1857R1 provides some useful comparison tables describing the scenarios affected by the change.
Global Module Fragment
Since the merging of Modules into C++20 there was another new concept introduced known as the global module fragment. The global module fragment is only used to compose module interfaces and the semantics of this area borrows semantics described in the Modules TS regarding entities attached to the global module. The purpose of the global module fragment is to serve as a space for users to put preprocessor directives like #include
‘s so that the module interface can compile, but the code in the global module fragment is not owned by or exported directly by the module interface. A quick example:
module; #include <string> #include <vector> export module m; export std::vector<std::string> f();
In this code sample the user wishes to use both vector
and string
but does not want to export them, they are simply an implementation detail of the function they wish to export, f
. The global module fragment in particular is the region of code between the module;
and export module m;
. In this region the only code which can be written are preprocessor directives; #if
and #define
are fair game. It is important to note that if the first two tokens of the translation unit are not module;
the interface unit is treated as though a global module fragment does not exist and this behavior is enforced through [cpp.global.frag]/1.
Module Partitions
Module partitions provide users with a new way of composing module interface units and organizing code of a module. At their very core, module partitions are pieces of a larger module interface unit and do not stand on their own as an interface to import outside of the module unit. Here is a quick example of a simple module interface which uses partitions:
m-part.ixx
export module m:part; export struct S { };
m.ixx
export module m; export import :part; export S f() { return { }; }
main.cpp
import m; // 'm' is also composed of partition ':part'. int main() { f(); }
To compile the sample:
cl /experimental:module /std:c++latest /c m-part.ixx
cl /experimental:module /std:c++latest /c m.ixx
cl /experimental:module /std:c++latest main.cpp m.obj
Notice that we did not explicitly add /module:reference
to any invocation of the compiler, this is because we have introduced a naming scheme for module partitions which ease the use of the feature—just like we have for normal module interface units where the filename represents the module name directly. The pattern that module partitions use is <primary-module-name>-<module-partition-name>
. If your module partitions follow that pattern the compiler can automatically find interface units for partitions. Of course, should you actually want to specify the module interfaces on the command line simply add the appropriate module:reference
arguments.
The standard refers to partitions in general as being interface units [module.unit]/3, however there is one exception and that is what we refer to as an “internal” partition. These internal partitions are not interfaces and only serve to facilitate the implementation details of a module unit. It is expressly ill-formed to export an internal partition (see translation unit 3 in section 4 of [module.unit]). MSVC implements the creation of internal partitions through a new switch /module:internalPartition
. An example of using an internal partition:
m-internals.cpp
note the .cpp extension
module m:internals; void g() { } // No declaration can have 'export' in an internal partition.
m.ixx
export module m; import :internals; // Cannot export this partition. export void f() { g(); }
To compile this interface:
cl /experimental:module /std:c++latest /module:internalPartition /c m-internals.cpp
cl /experimental:module /std:c++latest /c m.ixx
As previously mentioned, the :internals
partition can only be used to implement parts of the module interface m
and cannot contribute to it directly.
IntelliSense
(status as of Visual Studio 2019 version 16.6 Preview 2)
Keen readers might have noticed nascent understanding in IntelliSense for consuming modules. While still is far from full-fledged support for production and consumption of modules in the IDE – which we intend to provide as we move towards finalization of C++20 conformance, it shows initial capabilities which we are building on.
As soon as a translation unit consuming a module with an import
is configured using Property Pages for /std:c++latest
, /experimental:module
, and any necessary module lookup path options, and the imported module is generated, the IntelliSense processing should pick the relevant .ifc file up.
The existing support will recognize namespaces, free functions, and their parameters from the imported module after they are typed in the program. The names however will not be offered in the Autocomplete/Member List, and the processing will likely fail on other language constructs such as classes or templates.
Stay tuned as we expand the support in future releases!
Closing Thoughts
C++20 is bringing a lot of new concepts (literally and figuratively) to C++ and Modules are one of the largest contributors to how we will write code differently in the future. These MSVC conformance changes will help users facilitate the transition into thinking about how we organize and reason about interfaces to our APIs. As with all of our preview features the switches and compiler behavior with respect to modules are subject to change once we are ready to declare the toolset C++20 complete.
We urge you to go out and try using MSVC with Modules. 16.5 is available right now in preview through the Visual Studio 2019 downloads page!
As always, we welcome your feedback. Feel free to send any comments through e-mail at visualcpp@microsoft.com or through Twitter @visualc. Also, feel free to follow me on Twitter @starfreakclone.
If you encounter other problems with MSVC in VS 2019 please let us know via the Report a Problem option, either from the installer or the Visual Studio IDE itself. For suggestions or bug reports, let us know through DevComm.
Hello Cameron,
First, thank you all at the VS C++ team to be a motor of the evolution to c++ modules.
I tested header units with 16.7 Preview 1 (not tested with previous versions), and I get the following warning:
warning C5211: a reference to a header unit using '/module:reference' has been deprecated; prefer '/headerUnit' instead
Everything is fine after ignoring it, but unfortunately just replacing /module:reference by /headerUnit is not succeeding: the ifc is not detected...
I modified the example with the partitions as follows:
<code>
I build this with the following commands:
<code>
I get the following error:
<code>
Is there something wrong with my example? Or why doesn't this work?
I made it work by changing the last build command to the following:
The function get_it() is available in m-part.obj and not m.obj.
now we have modules,why we still need to declare before Implementation/Use?
if that,we should be need header file also (to solve declare problems) !!!
So what significance and benefits does the modules have for us?
When it comes to using header units with stdlib headers, how is a build system/generator supposed to determine the `header:ifc` mapping ahead of building, at configure time?
If you use `/module:showResolvedHeader /module:exportHeader ` for example, it tells you that mapping, but you've also now shifted all the work of generating the interface files to configure time, where it likely isn't parallelized.
Would it not be possible to make this easier to use, maybe something like `/module:search...
Modules support the progress is still very slow.
IntelliSense supports is the most important.
but almost 5 years(from vs 2015), there’s no substantial progress.
A couple of features (critical for me) are missed:
– there is no support for static CRT configurations;
– and no IntelliSense on modules.
Great to see more progress.
Is there any news on the STL modules? I had been trying them out and listed a bunch of issues here https://developercommunity.visualstudio.com/content/problem/32527/compiler-ice-when-using-stl-modules-on-vs-2017-150.html which was closed as fixed. However having tried then in 16.5 Preview 2 they are still to broken
to compile any large body of code.
Hi David,
The story around STL modules out of the box is still one that is in development. I expect we will have something more to say once we are approach C++20 completeness, but until then I can't give any specifics.
I apologize regarding the issues you hit in the compiler. Can you file a separate issue on the developer community? I have been working to get issues flushed out before C++20 comes to...
Happy to see import "foo.h" for the sake of transitional projects that need to deal with both headers and proper modules. Hopefully precompiled headers will soon be a memory.
If I'm reading this line right...
`/module:reference myheader.h:myheader.h.ifc main.cpp myheader.h.obj`
..then a new "module myheader.h;" is implicitly created? I expected that all the symbols within myheader.h would be imported into the global module space. Otherwise if another translation unit included that header (via normal #include), wouldn't duplicate symbols be...
Hi Dwayne,
Simply adding the will not implicitly create the .ifc for you—the build header unit output. You must have built it beforehand using .
When using header units it is important to note that any header unit created has ODR guarantees not offered through the traditional mechanism. Because of this it is best to avoid combining with with the same header file. In some cases duplicate symbols would be created—as...
I agree that it's best to not mix the two and instead convert #include's to imports. Though, in a large heterogeneous project where one module imports a header, it is also important to be able to #include that header elsewhere (say it's a referenced git submodule, not under your control, which has not adopted modules). I will play around with this some (#include and import of the same header) to see how well it works....