A Tour of C++ Modules in Visual Studio

Avatar

Will

C++ module support has arrived in Visual Studio! Grab the latest Visual Studio Preview if you want to try it out. C++ modules can help you compartmentalize your code, speed up build times, and they work seamlessly, side-by-side with your existing code.

This preview only supports C++ modules in the IDE for MSBuild projects. While the MSVC toolset is supported by any build system, Visual Studio’s IDE support for CMake doesn’t support C++ modules yet. We will let you know once it is! As always, please try it out and let us know if you have any feedback.

Module Basics

C++ modules allow you to closely control what is made available to the translation units that consume them. Unlike headers, they won’t leak macro definitions or private implementation details (no ridiculous prefixes needed). Also, unlike headers, they are built once and then can be consumed many times across your projects, reducing build overhead.

C++20 introduces new keywords to define and consume modules and Visual Studio uses a new file type “.ixx” to define a module’s interface. Read on for the details.

Getting Started with Modules in Visual Studio

If you created a brand-new project in the latest preview, you don’t need to do anything. However, before you can add or consume modules in existing projects you need to make sure you are using the latest C++ Language Standard.

To do this, set the C++ Language Standard to “Preview /std:c++latest”. If you have multiple projects in your solution, remember to do this for all of them.

General > C++ Language Standard, set to “Preview /std:c++latest”

And that’s it! You are ready to use C++ modules with Visual Studio.

Creating Modules

To add a module to a project you will need to create a module interface. These are normal C++ source files with the extension “.ixx”. They can include headers, import other modules, and will include the exported definitions of your module. You can add as many of these to a project as you want.

Add New Item > “C++ Module Interface Unit (.ixx)”

Here’s how this then looks in the Solution Explorer. In this example, the fib and printer projects both define C++ modules.

Solution Explorer with three module interfaces

Note: While this example shows all module interfaces in “.ixx” files, any C++ source file can be treated as a module interface. To do this, set the “Compile As” property on a source file to “Compile As Module”. The “Compile As” property can be found on the “Advanced” tab on any source file’s properties page.

Exporting Modules

So, what actually goes into a module interface? The example below defines a simple module called DefaultPrinter and exports a single struct:

module; //begins global module fragment

#include <iostream>

export module DefaultPrinter;

export struct DefaultPrinter
{
    void print_element(int e)
    {
        std::cout << e << " ";
    }

    void print_separator()
    {
        std::cout << ", ";
    }

    void print_eol()
    {
        std::cout << '\n';
    }
};

To break the example down a bit, you can see the new export syntax on lines 1, 5, and 7. Line 1 specifies that this is a module interface. Line 5 defines and exports the module itself and line 7 exports a struct. Each module can export many items, such structs, classes, functions, and templates.

Module interfaces can include headers and import other modules. When they are imported, they will not leak any details from these included headers or modules unless you explicitly import them. This isolation can help avoid naming collisions and leaking implementation details. You can safely define macros and use namespaces in module interfaces too. They will not leak like traditional headers.

To #include headers in a module interface, ensure you put them in the global module fragment between module; and export module mymodule;.

This example puts the implementation in the module’s interface, but that is optional. If you look back at the solution explorer before you can see the fibgen.ixx interface has a corresponding implementation in fibgen.cpp.

Its interface looks like this:

export module FibGenerator;
export fib gen_fib(int start, int &len);

With a corresponding implementation:

module FibGenerator;

fib gen_fib(int start, int &len)
{
	//...
}

Here, the interface defines the module name and exports gen_fib. The corresponding implementation uses the module keyword to define which module the implementation belongs to so everything can be combined into a cohesive unit automatically at build time.

Consuming Modules

To consume modules, use the new import keyword.

module;
#include <ranges>
#include <concepts>

import DefaultPrinter;

struct DefaultFormatter
{
    template<is_series S, is_printer T>
    void format(T t, S s)
    {
        while (!s.done())
        {
            t.print_element(s.next());
            t.print_separator();
        }
        t.print_eol();
    }
};

All exported items from the module interface will be available for use. This example makes use of the DefaultPrinter module in the first example, importing it on line 5.

Your code can consume modules in the same project or any referenced ones automatically (using project-to-project references to static library projects).

Consuming Modules from Other Modules

You can also import modules from another module interface. Here is an example that expands on the DefaultPrinter module above:

module;
#include <iostream>
import DefaultPrinter;

export module TabbedPrinter;

export struct TabbedPrinter : DefaultPrinter
{
    void print_separator()
    {
        std::cout << "\t";
    }
};

This example imports the DefaultPrinter module above and overrides its print_separator function. Other code can now import this TabbedPrinter without needing to worry about the details of DefaultPrinter. Visual Studio will make sure everything is built in the right order.

External Modules

It is also possible to reference modules that exist on disk, instead of ones belonging to another project in the solution. Care needs to be taken here, however, because modules are compiled, binary files. You must make sure they are compatible with the way you are building your projects.

You can tell Visual Studio to look for modules on disk by editing the Additional Module Dependencies property:

C/C++ > General > Additional Module Dependencies, you can add modules with “/reference[[module_name]=]path”, multiple modules can be separated by semicolons

IntelliSense and Modules

All the IntelliSense features you know and love also work with modules. Features like code completion, parameter help, Find All References, Go To Definition and Declaration, rename, and more all work across solutions the way you would expect when you use modules.

Here you can see Find All References and Peek Definition working with our TabbedPrinter module above. For instance, it can show all references of the DefaultPrinter structure exported from the DefaultPrinter module and display its definition:

Find All References Find All References of the “DefaultPrinter” structure across the solution

Peek Definition Peek Definition of the “DefaultPrinter” structure

You can also Go To or Peek the Definition of a module itself from anywhere that imports it:

Peek Definition of an imported module, "DefaultPrinter"

See Modules in Action

To see all of this in action, check out our modules demo from CppCon 2020. There are many other demos of the latest Visual Studio and C++20 features in action too if you are interested.

Header Units

A header unit is a standard C++ incantation to invoke the generation of metadata (IFC files) – for well-behaved header files, in particular standard library headers – similar to those generated for modules with the goal of speeding up overall build time, if done judiciously. However, unlike Modules, header units do not really provide isolation the way Modules do: macro definitions and other preprocessor states are still leaked to the consumers of the header units. You use a header unit via the import "header.h"; or import <header>; syntax. In Visual Studio, the metadata for header units are automatically generated by the build system. All items declared and reasonable definitions in the header file (and its includes) are made available to the consumer, as would an #include file. Like in the case of module consumption, macro definitions and other preprocessor states active in the code that imports a header unit will not influence the imported header unit in any way. However, unlike a module, any macro definition will be available for use in your code when you import a header unit. Header units are primarily a transition mechanism, not substitute for modules. If you have a chance to consider a named module vs. a header unit, we encourage you to invest the effort in designing proper modules. We will cover header units in depth in future blogs, especially their use in migrating existing codebases to uses of modules.

Full IDE and toolset support for header units is coming soon. You can track the status of header unit support for the Microsoft STL here on GitHub.

Feedback

If you are interested in trying out C++ modules with your own code, I urge you to grab the latest Visual Studio Preview. Please try it out and let use know if you have any questions or feedback. If you find any issues or have a suggestion, the best way to reach out to us is to Report a Problem.

27 comments

Leave a comment

  • Avatar
    madhusudan samantray

    1. What special treatment are given to headerfiles when they are placed at global module fragment .Is it not compiled to binary ?
    When i place it beyond it there is a linker error as the same headerfile is also included in manyplaces and header guard does not help.

    2. With advent of modules are pch going to be obsolete gradually ?

    3. How modules are going to be different from static libraries (.lib) ?

    • Krzysztof Wie
      Krzysztof Wie

      Hi, I’ll try to explain from what I understand, but am still having some knowledge holes, so don’t take it for granted.

      1. I don’t think these includes in the global module fragment are compiled. They might be internally by the compiler to be treated as modules (e.g. #include might be implicitly converted to #import ) but they are not compiled with the translation unit you compile. When you put those behind module declaration, then c++ compiler things (after preprocessing) these are declarations belonging to the module. And since msvc implements strong ownership model, the declarations are mangled into symbols containing module name. Hence the linker fails to link.

      2. I think so. However, keep in mind the pch are working with pre-C++20, so that’s one way or the other depending on the project requirements.

      3. Modules are tarnslation units, so their artifacts are the object files. Usually, you don’t distribute those (but caching is fine). They are introduced to componentarize your units of code.

      • Avatar
        GDRMicrosoft employee

        Krzysztof is right in his comment https://devblogs.microsoft.com/cppblog/a-tour-of-cpp-modules-in-visual-studio/#comment-1383

        Let me elaborate a bit further:
        a. There is no separate metadata (IFC files) generated for the `#include` in the global module fragment. This is one of the salient aspects of this feature. Global module fragments allow you to include legacy (usually system) header files that are not “modular” — think `”windows.h”` (or on Linux `”unistd.h”`) for example. They are processed as usual `#include`, because that is what they are.
        b. You can use the global module fragment to project a modular view over a subset of a very messy header files.
        c.. See also the recommendations in my CppCon 2019 talk: https://www.youtube.com/watch?v=tjSuKOz5HK4
        It has recommendations on when to use `#include` in the global module fragments, and when to use header units.

  • Avatar
    Thomas Farthing

    Hey Will,

    Thank you for the update, this is great news! I am really excited about modules, I think they will make a big difference for all of my work, so its great to see support coming along.

    I’ve had trouble finding information about the output of module compilation/linking and the use of this output in other programs. It might be good to update your blog post to clarify that when specifying “Additional Module Dependencies” that the targets of this are .ifc files (the binary module interface).

    Thank you again!

      • Avatar
        Thomas Farthing

        Ah, I hadn’t noticed that. So there are two settings, and this makes sense because it is consistent with how specifying external files for includes or linking works. One setting specifies directories to search (Additional BMI Directories), and the other specifies which specific module dependencies to add (Additional Module Dependencies). You don’t have to use the first, but if you don’t you need to put the full path of the module dependency.

        Additional Module Dependencies appear to want an .ifc file. For instance I use “Foo.ixx.ifc” in “Additional Module Dependencies”, which was an output file from the compilation of my Foo interface. I tried other values, for instance just Foo, but these didn’t work.

        • Avatar
          olgaarkMicrosoft employee

          Yes, Additional Module Dependencies (/reference switch) and Additional Header Unit Dependencies (/headerUnit) properties specify where to find BMI (.ifc) for a particular module or header unit.

          Note, that these properties are intended to be used only for modules built outside of the solution or for resolving collisions in modules coming from referenced projects. If you have several .ixx files in one project, the build will automatically figure out the dependencies between them, you don’t need to reference them explicitly. If you want to reference a module from a different static library project in the same solution, just add a reference to it.

  • Avatar
    Marius Mitea

    You mention that Intellisense should be working with the latest VS Preview and modules, right? I’ve been using modules in a regular VS solution and it compiles and runs fine, however Intellisense has no idea what the exported types are, everything is highlighted as an error.

    I’m fine with waiting for the final version of VS, but if you are implying in this post that the final version is pretty much what’s offered now, how true is it that Intellisense works with this?

  • Avatar
    Dwayne RobinsonMicrosoft employee

    > Unlike headers, they won’t leak macro definitions or private implementation details (no ridiculous prefixes needed).

    Indeed this is one of the most important aspect of modules for me, that no private implementation details leak out and pollute the namespace of the caller. So I played around with a pimpl class in Preview 16.8.0, but I see that internal details do still seem to leak out sometimes (but not other times), and I wonder if it’s just a bug in VS, or a design flaw in the modules TS?

    Example – this works fine, and the internal data type (not exported) doesn’t pollute the caller, and the caller can define its own symbol of the same name:

    ////////////////////
    // MyModule.ixx
    export module MyModule;
    
    using InternalType = int; // "InternalType" symbol not exported
    
    export class PublicClass
    {
    private:
        InternalType* i;
    };
    
    ////////////////////
    // Main.cpp
    import MyModule;
    
    class InternalType { /**/ }; // no name conflict 🙂
    
    PublicClass c;
    
    int wmain(int argc, wchar_t** argv)
    {
        return 0;
    }

    This however (almost identical, except the internal symbol is a `class` instead of `using`/`typedef` statement), reports “fatal error C1117: unrecoverable error importing module ‘MyModule’: symbol ‘InternalClass’ has already been defined”:

    ////////////////////
    // MyModule.ixx
    export module MyModule;
    
    class InternalType // explicitly *not* export'ed
    {
        int i;
    };
    
    export class PublicClass
    {
    private:
        InternalType* i;
    };
    
    ////////////////////
    // Main.cpp
    import MyModule;
    
    class InternalType // name conflict!
    {
        double d;
    };
    
    PublicClass c; // error here that InternalClass already defined. 🙁 But, but, they're in different modules?
    
    int wmain(int argc, wchar_t** argv)
    {
        return 0;
    }

    So, I still have to rename my internal class in one file or the other and/or add ridiculous prefixes. If this is resolved though, then we’ll finally get PIMPL cleanliness :D.

    • Avatar
      Andras Balogh

      I’m having the same issue, except in my case the leaking code is in the standard library (“symbol ‘unique_ptr’ has already been defined”), so there is no simple workaround. I see you have reported this back in January (https://developercommunity.visualstudio.com/content/problem/900116/c-modules-name-collision.html), but they still haven’t fixed it. I upvoted the issue, really hope they get around to it soon, as it hinders my ability to use modules at scale!

      • Avatar
        Dwayne RobinsonMicrosoft employee

        Andras: I value the upvote. Do you have your own `std::unique_ptr` class which clashes with the official std o_o, or do you mean just importing unique_ptr from <memory> from multiple places? If the latter, that sounds a little different from my case because the std::unique_ptr is really the same class in both cases. I’ve been #include’ing in the global module fragment (between “module;” and that file’s “export module MyModule;” statement) without issue. Eventually we’ll be able to just import std lib (and hopefully the linker correctly deduplicates the instances in a heterogeneous app that both imports and #includes classes like vector).

        • Avatar
          Andras Balogh

          I’m only using the standard unique_ptr, and I only ever #include anything in the global module fragment. I put my module test project aside for a bit, but will try to dig a little deeper and make a minimal repro case.