Modules (since C++20)

From cppreference.com
< cpp‎ | language
 
 
C++ language
General topics
Flow control
Conditional execution statements
if
Iteration statements (loops)
for
range-for (C++11)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications (until C++17*)
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
decltype (C++11)
auto (C++11)
alignas (C++11)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Implicit conversions - Explicit conversions
static_cast - dynamic_cast
const_cast - reinterpret_cast
Memory allocation
Classes
Class-specific function properties
explicit (C++11)
static
Special member functions
Templates
Miscellaneous
 
 

Most C++ projects use multiple translation units, and so they need to share declarations and definitions across those units. The usage of headers is prominent for this purpose, an example being the standard library whose declarations can be provided by including the corresponding header.

Modules are a language feature to share declarations and definitions across translation units. They are an alternative to some use cases of headers.

Modules are orthogonal to namespaces.

// helloworld.cpp
export module helloworld; // module declaration
 
import <iostream>;        // import declaration
 
export void hello()       // export declaration
{
    std::cout << "Hello world!\n";
}
// main.cpp
import helloworld; // import declaration
 
int main()
{
    hello();
}

Syntax

export(optional) module module-name module-partition (optional) attr (optional) ; (1)
export declaration (2)
export { declaration-seq (optional) } (3)
export(optional) import module-name attr (optional) ; (4)
export(optional) import module-partition attr (optional) ; (5)
export(optional) import header-name attr (optional) ; (6)
module; (7)
module : private; (8)
1) Module declaration. Declares that the current translation unit is a module unit.
2,3) Export declaration. Export all namespace-scope declarations in declaration or declaration-seq.
4,5,6) Import declaration. Import a module unit/module partition/header unit.

Module declarations

A translation unit may have a module declaration, in which case it is considered a module unit. The module declaration, if provided, must be the first declaration of the translation unit (excepted the global module fragment, which is covered later on). Each module unit is associated to a module name (and optionally a partition), provided in the module declaration.

export(optional) module module-name module-partition (optional) attr (optional) ;

The module name consists of one or more identifiers separated by dots (for example: mymodule, mymodule.mysubmodule, mymodule2...). Dots have no intrinsic meaning, however they are used informally to represent hierarchy.

A named module is the collection of module units with the same module name.

Module units whose declaration has the keyword export are termed module interface units; all other module units are termed module implementation units.

For every named module, there must be exactly one module interface unit that specifies no module partition; this module unit is termed the primary module interface unit. Its exported content will be available when importing the corresponding named module.

// (each line represents a separate translation unit)
 
export module A;   // declares the primary module interface unit for named module 'A'
module A;          // declares a module implementation unit for named module 'A'
module A;          // declares another module implementation unit for named module 'A'
export module A.B; // declares the primary module interface unit for named module 'A.B'
module A.B;        // declares a module implementation unit for named module 'A.B'

Exporting declarations and definitions

Module interface units can export declarations (including definitions), which can be imported by other translation units. To export a declaration, either prefix it with the export keyword, or else place it inside an export block.

export declaration
export { declaration-seq (optional) }
export module A; // declares the primary module interface unit for named module 'A'
 
// hello() will be visible by translations units importing 'A'
export char const* hello() { return "hello"; } 
 
// world() will NOT be visible.
char const* world() { return "world"; }
 
// Both one() and zero() will be visible.
export
{
    int one()  { return 1; }
    int zero() { return 0; }
}
 
// Exporting namespaces also works: hi::english() and hi::french() will be visible.
export namespace hi
{
    char const* english() { return "Hi!"; }
    char const* french()  { return "Salut!"; }
}

Importing modules and headers

Modules are imported via an import declaration:

export(optional) import module-name attr (optional) ;

All declarations and definitions exported in the module interface units of the given named module will be available in the translation unit using the import declaration.

Import declarations can be exported in a module interface unit. That is, if module A export-imports B, then importing A will also make visible all exports from B.

In module units, all import declarations (including export-imports) must be grouped after the module declaration and before all other declarations.

/////// A.cpp (primary module interface unit of 'A')
export module A;
 
export char const* hello() { return "hello"; }
 
/////// B.cpp (primary module interface unit of 'B')
export module B;
 
export import A;
 
export char const* world() { return "world"; }
 
/////// main.cpp (not a module unit)
#include <iostream>
import B;
 
int main()
{
    std::cout << hello() << ' ' << world() << '\n';
}

#include should not be used in a module unit (outside the global module fragment), because all included declarations and definitions would be considered part of the module. Instead, headers can also be imported with an import declaration:

export(optional) import header-name attr (optional) ;

Importing a header will make accessible all its definitions and declarations. Preprocessor macros are also accessible (because import declarations are recognized by the preprocessor). However, contrary to #include, preprocessing macros defined in the translation unit will not affect the processing of the header. This may be inconvenient in some cases (some headers use preprocessing macros as a form of configuration), in which case the usage of global module fragment is needed.

/////// A.cpp (primary module interface unit of 'A')
export module A;
 
import <iostream>;
export import <string_view>;
 
export void print(std::string_view message)
{
    std::cout << message << std::endl;
}
 
/////// main.cpp (not a module unit)
import A;
 
int main()
{
    std::string_view message = "Hello, world!";
    print(message);
}

Global module fragment

Module units can be prefixed by a global module fragment, which can be used to include headers when importing the headers is not possible (notably when the header uses preprocessing macros as configuration).

module;

preprocessing-directives (optional)

module-declaration

If a module-unit has a global module fragment, then its first declaration must be module;. Then, only preprocessing directives can appear in the global module fragment. Then, a standard module declaration marks the end of the global module fragment and the start of the module content.

/////// A.cpp (primary module interface unit of 'A')
module;
 
// Defining _POSIX_C_SOURCE adds functions to standard headers,
// according to the POSIX standard.
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
 
export module A;
 
import <ctime>;
 
// Only for demonstration (bad source of randomness).
// Use C++ <random> instead.
export double weak_random()
{
    std::timespec ts;
    std::timespec_get(&ts, TIME_UTC); // from <ctime>
 
    // Provided in <stdlib.h> according to the POSIX standard.
    srand48(ts.tv_nsec);
 
    // drand48() returns a random number between 0 and 1.
    return drand48();
}
 
/////// main.cpp (not a module unit)
import <iostream>;
import A;
 
int main()
{
    std::cout << "Random value between 0 and 1: " << weak_random() << '\n';
}

Private module fragment

Primary module interface unit can be suffixed by a private module fragment, which allows a module to be represented as a single translation unit without making all of the contents of the module reachable to importers.

module : private;

declaration-seq (optional)

Private module fragment ends the portion of the module interface unit that can affect the behavior of other translation units. If a module unit contains a private module fragment, it will be the only module unit of its module.

export module foo;
 
export int f();
 
module : private; // ends the portion of the module interface unit that
                  // can affect the behavior of other translation units
                  // starts a private module fragment
 
int f()           // definition not reachable from importers of foo
{
    return 42;
}

Module partitions

A module can have module partition units. They are module units whose module declarations include a module partition, which starts with a colon : and is placed after the module name.

export module A:B; // Declares a module interface unit for module 'A', partition ':B'.

A module partition represents exactly one module unit (two module units cannot designate the same module partition). They are visible only from inside the named module (translation units outside the named module cannot import a module partition directly).

A module partition can be imported by module units of the same named module.

export(optional) import module-partition attr (optional) ;
/////// A-B.cpp   
export module A:B;
...
 
/////// A-C.cpp
module A:C;
...
 
/////// A.cpp
export module A;
 
import :C;
export import :B;
 
...

All definitions and declarations in a module partition are visible by the importing module unit, whether exported or not.

Module partitions can be module interface units (when their module declarations have export). They must be export-imported by the primary module interface unit, and their exported statements will be visible when the module is imported.

export(optional) import module-partition attr (optional) ;
///////  A.cpp   
export module A;     // primary module interface unit
 
export import :B;    // Hello() is visible when importing 'A'.
import :C;           // WorldImpl() is now visible only for 'A.cpp'.
// export import :C; // ERROR: Cannot export a module implementation unit.
 
// World() is visible by any translation unit importing 'A'.
export char const* World()
{
    return WorldImpl();
}
/////// A-B.cpp 
export module A:B; // partition module interface unit
 
// Hello() is visible by any translation unit importing 'A'.
export char const* Hello() { return "Hello"; }
/////// A-C.cpp 
module A:C; // partition module implementation unit
 
// WorldImpl() is visible by any module unit of 'A' importing ':C'.
char const* WorldImpl() { return "World"; }
/////// main.cpp 
import A;
import <iostream>;
 
int main()
{
    std::cout << Hello() << ' ' << World() << '\n';
    // WorldImpl(); // ERROR: WorldImpl() is not visible.
}

Module ownership

In general, if a declaration appears after the module declaration in a module unit, it is attached to that module.

If a declaration of an entity is attached to a named module, that entity can only be defined in that module. All declarations of such an entity must be attached to the same module.

If a declaration is attached to a named module, and it is not exported, the declared name has module linkage.

export module lib_A;
 
int f() { return 0; } // f has module linkage
export int x = f();   // x equals 0
export module lib_B;
 
int f() { return 1; } // OK, f in lib_A and f in lib_B refer to different entities
export int y = f(); // y equals 1

If two matching declarations are attached to different modules, and they both declare names with external linkage, the program is ill-formed; no diagnostic is required if neither is reachable from the other. In practice, there are two models:

  • In the weak module ownership model, such declarations are considered to declare the same entity.
  • In the strong module ownership model, they are considered to declare different entities.
export module lib_A;
 
export constexpr int f() { return 0; } // f has external linkage
export module lib_B;
 
export constexpr int f() { return 1; }
// In the weak ownership model: multiple definitions of f; the linker may pick either
// In the strong ownership model: OK, f in lib_A and f in lib_B are different entities

The following declarations are not attached to any named module (and thus the declared entity can be defined outside the module):

export module lib_A;
 
namespace ns // ns is not attached to lib_A.
{
    export extern "C++" int f(); // f is not attached to lib_A.
           extern "C++" int g(); // g is not attached to lib_A.
    export              int h(); // h is attached to lib_A.
}
// ns::h must be defined in lib_A, but ns::f and ns::g can be defined elsewhere (e.g.
// in a traditional source file).

Notes

Feature-test macro Value Std Comment
__cpp_modules 201907L (C++20) Modules — core language support
__cpp_lib_modules 202207L (C++23) Standard library modules std and std.compat

Keywords

export, import, module, private