C++ Modules conformance improvements with MSVC in Visual Studio 2019 16.5

Cameron

January 22nd, 2020

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 and import keywords provide users with more flexibility when using these terms as identifiers in code.

and 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

", 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.