C++ Modules in VS 2015 Update 1

Andrew Pardoe

点这里看中文版

[This post was written by Gabriel Dos Reis and Andrew Pardoe]

Update: See this post on using the Standard Library via modules in MSVC.

The VC++ team is excited to preview a new feature in VS 2015 Update 1: The first experimental implementation of A Module System for C++, proposed for C++17. That proposal was approved by the C++ standards Evolution Working Group for a C++17 Technical Specification at the Fall 2015 meeting in Kona, Hawai’i. The draft wording for the Technical Specification is under review by the C++ Standards Core Working Group.

Modules are often talked about as a way to speed up compilation. But they have far more to offer than just build performance! C++ Modules can help you improve componentization of your code. In fact, componentization is an effective road to compilation and build throughput. Modules allow you to isolate your code from hard-to-debug preprocessor state. And they make it easier to develop tools to work with your code.

Modules allow you to express the symbolic dependency your component is taking on providers of functionalities it needs, and the boundary of that dependency, directly in code. Modules eliminate the need for convoluted preprocessor voodoo to specify interfaces through header files. The preprocessor is there for you and works together with the modules system if you have no other choice but to work with macro-based interfaces, conditional compilation and code-generation. The preprocessor is a powerful beast with no structure and C++ Modules aren’t designed to do everything that it does. The preprocessor’s job is to generate pre-tokens for the lexer to consume. It has been put to creative usages, for more than 40 years, to generate various sorts of text, including HTML source documents.

If you want to know more about the principles and rationale behind the design choices of C++ Modules, stop now and read the proposal: A Module System for C++ .There’s also a great talk on C++ Modules by Gabriel Dos Reis from CppCon 2015. The video is on the CppCon YouTube channel; the slides from the talk are also available in an IsoCpp GitHub repo. But if you want to jump right in and see what this feature has to offer, read on!

The implementation in Visual C++ 2015 Update 1 is a preview of an ongoing effort so we’re eager to hear your feedback. This is a chance for you to have impact on a major C++ feature. We want to build a C++ modules system that works for all developers with all compilers so please let us know if you have any feedback. You can reach us at modules@microsoft.com.

Modules Support in Visual C++ 2015 Update 1

First, you should know that module support is completely guarded by a switch: /experimental:module. If you don’t use this switch then the modules feature won’t affect your code at all. Also, be aware that support for modules is only in the command-line tools right now. A lot of IDE functionality should just work but full IDE integration isn’t there yet. Also, this preview’s focus is on componentization. As we advance in completing support for C++ language features, we will be increasing our focus to optimizing build throughput in the module support; without componentization first, it would be far too easy to duplicate a particular build logic without making significant dents into the fundamental problems.

Producing Modules

Creating and using a module is straightforward: the developer merely declares a source file to contain the definition of a module by writing module M;. She then announces what parts of the source file are part of the module’s interface by preceding each and every single declaration of those entities with the export keyword.

Any top-level declaration can be exported or any sequence of top-level declarations contained in braces. Modules do not define new namespaces or change name lookup rules in any way. They simply allow the developer to specify and publish the interface for a source file. So, there is really no new name lookup rules you need to learn.

In this example, the functions f(int) and g(double, int) are exported as part of the interface of module M.

// file: foo.ixx module M; export int f(int x) {     return 2 + x; } export double g(double y, int z) {     return y * z; }

The command line to compile the module interface is just cl /c /experimental:module foo.ixx. The extension “ixx” is special. It tells the compiler that the content of the source file is supposed to be a module interface source code. If you want to use another extension on your interface file you also have to supply the switch/module:interface. (Note these switches are subject to change in the future!)

When you compile a module interface file, you get an OBJ file as usual. The compiler also produces a file with extension “.ifc”(called an IFC file) that contains a metadata description of the module interface. This is the only time that the module support produces anything additional to what a traditional compilation would do. The binary format of the IFC file will be open source; it is modelled after the Internal Program Representation work done by Gabriel Dos Reis and Bjarne Stroustrup a decade ago. The original implementation of the IPR is open source and can be found in the IPR repo on GitHub.

Consuming Modules

To consume the module, the developer writes import M; at the top of a source file, thus making the declarations of f(int) and g(double, int) visible in the source file. The compiler switch to specify a compiled binary module interface file is /module:reference.

// file: bar.cpp import M; int main() { f(5); g(0.0, 1);     return 0; }

Compile bar.cpp with the command line cl /experimental:module /module:reference M.ifc bar.cpp foo.obj. The function definitions for f(int) and g(double, int) are available in bar.cpp because of the import statement. If there are too many reference files, or if you put all your IFC files in a given directory, you can instead use the compiler option /module:search which takes a directory name as its argument.

Overview

At the file level, the source file containing the module definition is called foo.ixx. (The name of the module definition, M in our example, doesn’t need to match the name of the file, foo.) Compiling foo.ixx creates M.ifc, the module interface file, which is a binary representation of the interface, in addition to foo.obj, the standard Windows object file.

When consuming a module (with the /module:reference switch), the compiler reads in M.ifc to make the top-level exported names in the interface available to the source file currently being compiled, and the linker consumes foo.obj as usual.

These three keywords and two switches are enough to let you experiment with C++ Modules. There are a few more switches available for transitional scenarios but they’re not something to depend upon as they might change as the design evolves.

Edit 4 Oct 2016: We’ve had some interest in specifying the output directory and interface file name. That option is simply /module:output, as follows:   cl /experimental:module /module:export /module:name ModuleName /module:wrapper C:\Output\path\ModuleName.h /module:output C:\Output\path\ModuleName.ifc -c <source-file>

Convenience Functions

It’s important that C++ Modules be incrementally adoptable in your source code. We’ve created a few convenience functions to help with migration scenarios. All of these functions are being tested by teams inside of Microsoft. They will likely change based on internal and external developer feedback. We’ve also created some tooling to help manipulate module interface files that we’ll discuss in another blog post.

Consuming Existing Legacy Header Files as Module Interfaces

Say you have an existing source file (in particular header file) that is well-behaved in terms of macros and preprocessor state. You’d like to make that header consumable as if it was a module interface. We’ve build a convenience into the compiler that allows you to pretend that all top-level declarations with external linkage in a C++ source file were marked as exported. You use the switches /module:name and /module:export to automatically create a compiled module interface (IFC) from a header. The argument to /module:name specifies the name of the module interface (.IFC) and the argument to /module:export specifies which header file should be consumed to create the module interface. Note that you currently have to include your header in a .cpp file (or rename your header) because of a limitation in our compiler’s file handling.

// file: foo.cpp int f(int x) {     return 2 + x; } double g(double y, int z) {     return y * z; }

The command line cl /c /experimental:module /module:name mymodule /module:export foo.cpp produces mymodule.ifc, an interface file with definitions for f(int) and g(double, int).

Module Search Path

/module:search indicates a directory where the compiler should search for files referenced via /module:reference. For example, the compilation command line for bar.cpp above (in Consuming Modules) could have been written as cl /experimental:module /module:search . bar.cpp foo.obj to search the current directory for the IFC file.

Preserving Macros

Lastly, if your legacy source file defines macros that are essential to its consumers, you can have the compiler generate a wrapper header file containing an import declaration followed by a preprocessor definition of those macros. The compiler switch /module:exportActiveMacros exports all macro definitions that are active when the compiler finishes compiling the source file that defines the module. If you want to be selective you can use /module:exportMacro instead, where designates the macro you want to be defined in the wrapper file. The name of the wrapper header file is specified via the switch /module:wrapper which takes a filename argument.

// file: baz.h #ifndef BAR_INCLUDED #define number 6 int f(int x) {     return 2 + x; } double g(double y, int z) {     return y * z; } #endif // BAR_INCLUDED

// file: baz.cpp #include “baz.h”

Compiling the above source files with cl /c /experimental:module /module:export /module:name mymodule baz.cpp /module:wrapper baz-wrapper.h /module:exportActiveMacros will produce mymodule.ifc, as expected, but will additionally produce a header file, baz-wrapper.h, containing the following:

#ifndef mymodule_WRAPPER_INCLUDED #define mymodule_WRAPPER_INCLUDED import mymodule; #define number 6 #endif // mymodule_WRAPPER_INCLUDED

You can now include baz-wrapper.h instead of baz.h and get the componentization benefits of modules even if baz.h wasn’t written as a module in the first place. This technique preserves your preprocessor state and gives you a chance to clean up any macros that you might not have realized were active at the time. Sadly, sorting through rogue preprocessor state is an all too common experience.

Have Fun!

C++ Modules in VS 2015 Update 1 are an exciting feature, even in this early state. There’s more goodness to come—obviously we’re lacking some basic functionality such as integration with the VS IDE and build system—but we wanted to get something out there early so that our developer community could have a chance to impact the work in a big way. Please try out C++ Modules and let us know your thoughts at modules@microsoft.com.

0 comments

Discussion is closed.

Feedback usabilla icon