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

Avatar

Cameron

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

12 comments

Comments are closed.

  • Avatar
    Dwayne RobinsonMicrosoft logo

    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 pulled into the translation unit, yielding code bloat?

    • Avatar
      Cameron DaCamara

      Hi Dwayne,

      Simply adding the /module:reference myheader.h:myheader.h.ifc will not implicitly create the .ifc for you—the build header unit output. You must have built it beforehand using /module:exportHeader myheader.h.

      When using header units it is important to note that any header unit created has ODR guarantees not offered through the traditional #include mechanism. Because of this it is best to avoid combining import with #include with the same header file. In some cases duplicate symbols would be created—as you point out—in other, more nefarious, cases you will end up with an ODR violation of some kind which may or may not manifest at link time.

      • Avatar
        Dwayne RobinsonMicrosoft logo

        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. Thanks.

    • Avatar
      Cameron DaCamara

      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 a close.

  • Avatar
    Melak XLVII

    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 build/modules /module:reference iostream:iostream.ifc`,
    rather than having to specify the full path for the stdlib headers in order to specify the mapping to the interface file name?

  • Avatar
    gf s

    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?

  • Avatar
    Marius Bancila

    I modified the example with the partitions as follows:

    // m-part.ixx
    
    export module m:part;
    
    export int get_it()
    {
        return 42;
    }
    
    // m.ixx
    
    export module m;
    
    export import :part;
    
    // main.cpp
    
    import std.core;
    import m;
    
    int main()
    {
       std::cout << get_it() << '\n';
    }

    I build this with the following commands:

    cl /experimental:module /std:c++latest /EHsc /MD /c m-part.ixx
    cl /experimental:module /std:c++latest /EHsc /MD /c m.ixx
    cl /experimental:module /std:c++latest /EHsc /MD main.cpp m.obj

    I get the following error:

    /out:main.exe
    main.obj
    m.obj
    main.obj : error LNK2019: unresolved external symbol "int __cdecl get_it(void)" (?get_it@@YAHXZ) referenced in function _main
    main.exe : fatal error LNK1120: 1 unresolved externals

    Is there something wrong with my example? Or why doesn’t this work?

    • Avatar
      Marius Bancila

      I made it work by changing the last build command to the following:

      cl /experimental:module /std:c++latest /EHsc /MD main.cpp m-part.obj

      The function get_it() is available in m-part.obj and not m.obj.

  • Avatar
    Onduril

    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 as a valid header unit reference.
    I also found the option /module:output to be valuable but it’s barely referenced in the different docs and blog posts.
    Is there a page with all the command line options relatives to modules, or more generally, what’s the best source to be kept up to date as the VS module support evolve ?