Namespaces
Variants

Modules (since C++20)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
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
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

La mayoría de los proyectos en C++ utilizan múltiples unidades de traducción, por lo que necesitan compartir declaraciones y definiciones entre esas unidades. El uso de headers es prominente para este propósito, un ejemplo siendo la biblioteca estándar cuyas declaraciones pueden ser proporcionadas incluyendo el header correspondiente .

Los módulos son una característica del lenguaje para compartir declaraciones y definiciones entre unidades de traducción. Son una alternativa para algunos casos de uso de los encabezados.

Los módulos son ortogonales a namespaces .

// helloworld.cpp
export module helloworld; // declaración del módulo
import <iostream>;        // declaración de importación
export void hello()       // declaración de exportación
{
    std::cout << "Hello world!\n";
}
// main.cpp
import helloworld; // declaración de importación
int main()
{
    hello();
}

Contenidos

Sintaxis

export (opcional) module module-name module-partition  (opcional) attr  (opcional) ; (1)
export declaration (2)
export { declaration-seq  (opcional) } (3)
export (opcional) import module-name attr  (opcional) ; (4)
export (opcional) import module-partition attr  (opcional) ; (5)
export (opcional) import header-name attr  (opcional) ; (6)
module; (7)
module : private; (8)
1) Declaración de módulo. Declara que la unidad de traducción actual es una module unit .
2,3) Declaración de exportación. Exporta todas las declaraciones en ámbito de namespace en declaration o declaration-seq .
4,5,6) Declaración de importación. Importa una unidad de módulo/partición de módulo/unidad de cabecera.

Declaraciones de módulos

Una unidad de traducción puede tener una declaración de módulo, en cuyo caso se considera una unidad de módulo . La declaración de módulo , si se proporciona, debe ser la primera declaración de la unidad de traducción (exceptuando el fragmento de módulo global , que se cubrirá más adelante). Cada unidad de módulo está asociada a un nombre de módulo (y opcionalmente una partición), proporcionado en la declaración de módulo.

export (opcional) module nombre-módulo partición-módulo  (opcional) attr  (opcional) ;

El nombre del módulo consiste en uno o más identificadores separados por puntos (por ejemplo: mymodule , mymodule.mysubmodule , mymodule2 ...). Los puntos no tienen un significado intrínseco, sin embargo se utilizan informalmente para representar jerarquía.

Si cualquier identificador en el nombre del módulo o partición del módulo está definido como una macro de tipo objeto , el programa está mal formado.

Un módulo nombrado es la colección de unidades de módulo con el mismo nombre de módulo.

Las unidades de módulo cuya declaración tiene la palabra clave export se denominan unidades de interfaz de módulo ; todas las demás unidades de módulo se denominan unidades de implementación de módulo .

Para cada módulo nombrado, debe existir exactamente una unidad de interfaz de módulo que no especifique ninguna partición de módulo; esta unidad de módulo se denomina unidad de interfaz de módulo primaria . Su contenido exportado estará disponible al importar el módulo nombrado correspondiente.

// (cada línea representa una unidad de traducción separada)
export module A;   // declara la unidad de interfaz del módulo primario para el módulo nombrado 'A'
module A;          // declara una unidad de implementación del módulo para el módulo nombrado 'A'
module A;          // declara otra unidad de implementación del módulo para el módulo nombrado 'A'
export module A.B; // declara la unidad de interfaz del módulo primario para el módulo nombrado 'A.B'
module A.B;        // declara una unidad de implementación del módulo para el módulo nombrado 'A.B'

Exportación de declaraciones y definiciones

Las unidades de interfaz de módulo pueden exportar declaraciones (incluyendo definiciones), que pueden ser importadas por otras unidades de traducción. Para exportar una declaración, ya sea prefíjela con la palabra clave export , o colóquela dentro de un bloque export .

export declaración
export { secuencia-de-declaraciones  (opcional) }
export module A; // declara la unidad de interfaz del módulo primario para el módulo nombrado 'A'
// hello() será visible para las unidades de traducción que importen 'A'
export char const* hello() { return "hello"; } 
// world() NO será visible.
char const* world() { return "world"; }
// Tanto one() como zero() serán visibles.
export
{
    int one()  { return 1; }
    int zero() { return 0; }
}
// Exportar espacios de nombres también funciona: hi::english() y hi::french() serán visibles.
export namespace hi
{
    char const* english() { return "Hi!"; }
    char const* french()  { return "Salut!"; }
}

Importación de módulos y unidades de cabecera

Los módulos se importan mediante una declaración de importación :

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

Todas las declaraciones y definiciones exportadas en las unidades de interfaz del módulo del módulo nombrado dado estarán disponibles en la unidad de traducción que utilice la declaración de importación.

Las declaraciones de importación pueden ser exportadas en una unidad de interfaz de módulo. Es decir, si el módulo B exporta-importa A , entonces importar B también hará visibles todas las exportaciones de A .

En unidades de módulo, todas las declaraciones de importación (incluyendo export-imports) deben agruparse después de la declaración del módulo y antes de todas las demás declaraciones.

/////// A.cpp (unidad de interfaz de módulo primario de 'A')
export module A;
export char const* hello() { return "hello"; }
/////// B.cpp (unidad de interfaz de módulo primario de 'B')
export module B;
export import A;
export char const* world() { return "world"; }
/////// main.cpp (no es una unidad de módulo)
#include <iostream>
import B;
int main()
{
    std::cout << hello() << ' ' << world() << '\n';
}

#include no debe usarse en una unidad de módulo (fuera del global module fragment ), porque todas las declaraciones y definiciones incluidas serían consideradas parte del módulo. En su lugar, los encabezados también pueden importarse como header units con una import declaration :

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

Una unidad de cabecera es una unidad de traducción separada sintetizada a partir de un encabezado. Importar una unidad de cabecera hará accesibles todas sus definiciones y declaraciones. Las macros del preprocesador también son accesibles (porque las declaraciones de importación son reconocidas por el preprocesador).

Sin embargo, a diferencia de #include , las macros de preprocesamiento ya definidas en el punto de la declaración de importación no afectarán el procesamiento del encabezado. Esto puede ser inconveniente en algunos casos (algunos encabezados utilizan macros de preprocesamiento como forma de configuración), en cuyo caso se necesita el uso del fragmento de módulo global .

/////// A.cpp (unidad de interfaz de módulo primario de 'A')
export module A;
import <iostream>;
export import <string_view>;
export void print(std::string_view message)
{
    std::cout << message << std::endl;
}
/////// main.cpp (no es una unidad de módulo)
import A;
int main()
{
    std::string_view message = "Hello, world!";
    print(message);
}

Fragmento de módulo global

Las unidades de módulo pueden tener como prefijo un fragmento de módulo global , que puede utilizarse para incluir cabeceras cuando importar las cabeceras no es posible (especialmente cuando la cabecera utiliza macros de preprocesamiento como configuración).

module;

preprocessing-directives  (opcional)

module-declaration

Si un módulo-unidad tiene un fragmento de módulo global, entonces su primera declaración debe ser module; . Luego, solo directivas de preprocesamiento pueden aparecer en el fragmento de módulo global. Después, una declaración de módulo estándar marca el final del fragmento de módulo global y el inicio del contenido del módulo.

/////// A.cpp (unidad de interfaz de módulo primario de 'A')
module;
// Definir _POSIX_C_SOURCE añade funciones a los encabezados estándar,
// según el estándar POSIX.
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
export module A;
import <ctime>;
// Solo para demostración (fuente débil de aleatoriedad).
// Usar C++ <random> en su lugar.
export double weak_random()
{
    std::timespec ts;
    std::timespec_get(&ts, TIME_UTC); // desde <ctime>
    // Proporcionado en <stdlib.h> según el estándar POSIX.
    srand48(ts.tv_nsec);
    // drand48() retorna un número aleatorio entre 0 y 1.
    return drand48();
}
/////// main.cpp (no es una unidad de módulo)
import <iostream>;
import A;
int main()
{
    std::cout << "Valor aleatorio entre 0 y 1: " << weak_random() << '\n';
}

Fragmento de módulo privado

La unidad de interfaz del módulo principal puede tener como sufijo un fragmento privado del módulo , lo que permite que un módulo se represente como una sola unidad de traducción sin hacer que todos los contenidos del módulo sean accesibles para los importadores.

module : private;

declaration-seq  (opcional)

Fragmento privado del módulo finaliza la porción de la unidad de interfaz del módulo que puede afectar el comportamiento de otras unidades de traducción. Si una unidad de módulo contiene un fragmento privado del módulo , será la única unidad de módulo de su módulo.

export module foo;
export int f();
module : private; // finaliza la porción de la unidad de interfaz del módulo que
                  // puede afectar el comportamiento de otras unidades de traducción
                  // inicia un fragmento privado del módulo
int f()           // definición no accesible desde los importadores de foo
{
    return 42;
}

Particiones de módulos

Un módulo puede tener unidades de partición de módulo . Son unidades de módulo cuyas declaraciones de módulo incluyen una partición de módulo, que comienza con dos puntos : y se coloca después del nombre del módulo.

export module A:B; // Declara una unidad de interfaz de módulo para el módulo 'A', partición ':B'.

Una partición de módulo representa exactamente una unidad de módulo (dos unidades de módulo no pueden designar la misma partición de módulo). Son visibles únicamente desde dentro del módulo nombrado (las unidades de traducción fuera del módulo nombrado no pueden importar una partición de módulo directamente).

Una partición de módulo puede ser importada por unidades de módulo del mismo módulo nombrado.

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

Todas las definiciones y declaraciones en una partición de módulo son visibles para la unidad de módulo importadora, ya sean exportadas o no.

Las particiones de módulo pueden ser unidades de interfaz de módulo (cuando sus declaraciones de módulo tienen export ). Deben ser export-importadas por la unidad de interfaz de módulo principal, y sus declaraciones exportadas serán visibles cuando el módulo sea importado.

export (opcional) import module-partition attr  (opcional) ;
///////  A.cpp   
export module A;     // unidad de interfaz primaria del módulo
export import :B;    // Hello() es visible al importar 'A'.
import :C;           // WorldImpl() ahora es visible solo para 'A.cpp'.
// export import :C; // ERROR: No se puede exportar una unidad de implementación de módulo.
// World() es visible para cualquier unidad de traducción que importe 'A'.
export char const* World()
{
    return WorldImpl();
}
/////// A-B.cpp 
export module A:B; // unidad de interfaz de módulo particionado
// Hello() es visible para cualquier unidad de traducción que importe 'A'.
export char const* Hello() { return "Hello"; }
/////// A-C.cpp 
module A:C; // unidad de implementación del módulo particionado
// WorldImpl() es visible por cualquier unidad de módulo de 'A' que importe ':C'.
char const* WorldImpl() { return "World"; }
/////// main.cpp 
import A;
import <iostream>;
int main()
{
    std::cout << Hello() << ' ' << World() << '\n';
    // ERROR: WorldImpl() no es visible.
}

Propiedad del módulo

En general, si una declaración aparece después de la declaración del módulo en una unidad de módulo, se adjunta a ese módulo.

Si una declaración de una entidad está adjunta a un módulo con nombre, esa entidad solo puede definirse en ese módulo. Todas las declaraciones de dicha entidad deben estar adjuntas al mismo módulo.

Si una declaración está adjunta a un módulo con nombre, y no es exportada, el nombre declarado tiene module linkage .

export module lib_A;
int f() { return 0; } // f tiene enlace de módulo
export int x = f();   // x es igual a 0
export module lib_B;
int f() { return 1; } // OK, f en lib_A y f en lib_B se refieren a entidades diferentes
export int y = f(); // y es igual a 1

Si dos declaraciones de una entidad están adjuntas a módulos diferentes, el programa está mal formado; no se requiere diagnóstico si ninguna es accesible desde la otra.

/////// decls.h
int f(); // #1, adjunta al módulo global
int g(); // #2, adjunta al módulo global
/////// Interfaz del módulo M
module;
#include "decls.h"
export module M;
export using ::f; // OK, no declara una entidad, exporta #1
int g();          // Error: coincide con #2, pero está adjunto a M
export int h();   // #3
export int k();   // #4
/////// Otra unidad de traducción
import M;
static int h();   // Error: coincide con #3
int k();          // Error: coincide con #4

Las siguientes declaraciones no están adjuntas a ningún módulo con nombre (y por lo tanto la entidad declarada puede definirse fuera del módulo):

export module lib_A;
namespace ns // ns no está adjunto a lib_A.
{
    export extern "C++" int f(); // f no está adjunto a lib_A.
           extern "C++" int g(); // g no está adjunto a lib_A.
    export              int h(); // h está adjunto a lib_A.
}
// ns::h debe definirse en lib_A, pero ns::f y ns::g pueden definirse en otro lugar (por ejemplo,
// en un archivo fuente tradicional).

Notas

Macro de prueba de características Valor Estándar Característica
__cpp_modules 201907L (C++20) Módulos — soporte del lenguaje central
__cpp_lib_modules 202207L (C++23) Módulos de la biblioteca estándar std y std. compat

Palabras clave

private , module , import , export

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 2732 C++20 no estaba claro si los headers importables pueden
reaccionar al estado del preprocesador desde el punto de importación
no reacciona
P3034R1 C++20 los nombres de módulo y las particiones de módulo podían
contener identificadores definidos como macros tipo objeto
prohibido