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 plupart des projets C++ utilisent plusieurs unités de traduction, et ont donc besoin de partager les déclarations et les définitions entre ces unités. L'utilisation des en-têtes est prédominante à cette fin, un exemple étant la bibliothèque standard dont les déclarations peuvent être fournies en incluant l'en-tête correspondant .

Les modules sont une fonctionnalité du langage permettant de partager des déclarations et des définitions entre les unités de traduction. Ils constituent une alternative à certains cas d'utilisation des en-têtes.

Les modules sont orthogonaux aux namespaces .

// helloworld.cpp
export module helloworld; // déclaration de module
import <iostream>;        // déclaration d'importation
export void hello()       // déclaration d'exportation
{
    std::cout << "Hello world!\n";
}
// main.cpp
import helloworld; // déclaration d'importation
int main()
{
    hello();
}

Table des matières

Syntaxe

export (facultatif) module nom-module partition-module  (facultatif) attr  (facultatif) ; (1)
export déclaration (2)
export { séquence-déclarations  (facultatif) } (3)
export (facultatif) import nom-module attr  (facultatif) ; (4)
export (facultatif) import partition-module attr  (facultatif) ; (5)
export (facultatif) import nom-en-tête attr  (facultatif) ; (6)
module; (7)
module : private; (8)
1) Déclaration de module. Déclare que l'unité de traduction actuelle est une unité de module .
2,3) Déclaration d'exportation. Exporte toutes les déclarations au niveau de l'espace de noms dans declaration ou declaration-seq .
4,5,6) Déclaration d'importation. Importe une unité de module/partition de module/unité d'en-tête.

Déclarations de module

Une unité de traduction peut avoir une déclaration de module, auquel cas elle est considérée comme une unité de module . La déclaration de module , si elle est fournie, doit être la première déclaration de l'unité de traduction (à l'exception du fragment de module global , qui sera abordé ultérieurement). Chaque unité de module est associée à un nom de module (et optionnellement une partition), fourni dans la déclaration de module.

export (facultatif) module nom-module partition-module  (facultatif) attr  (facultatif) ;

Le nom du module est constitué d'un ou plusieurs identifiants séparés par des points (par exemple : mymodule , mymodule.mysubmodule , mymodule2 ...). Les points n'ont pas de signification intrinsèque, cependant ils sont utilisés informellement pour représenter une hiérarchie.

Si un identifiant dans le nom du module ou la partition de module est défini comme une macro de type objet , le programme est mal formé.

Un module nommé est l'ensemble des unités de module portant le même nom de module.

Les unités de module dont la déclaration comporte le mot-clé export sont appelées unités d'interface de module ; toutes les autres unités de module sont appelées unités d'implémentation de module .

Pour chaque module nommé, il doit y avoir exactement une unité d'interface de module qui ne spécifie aucune partition de module ; cette unité de module est appelée unité d'interface de module primaire . Son contenu exporté sera disponible lors de l'importation du module nommé correspondant.

// (chaque ligne représente une unité de traduction séparée)
export module A;   // déclare l'unité d'interface de module primaire pour le module nommé 'A'
module A;          // déclare une unité d'implémentation de module pour le module nommé 'A'
module A;          // déclare une autre unité d'implémentation de module pour le module nommé 'A'
export module A.B; // déclare l'unité d'interface de module primaire pour le module nommé 'A.B'
module A.B;        // déclare une unité d'implémentation de module pour le module nommé 'A.B'

Exportation des déclarations et définitions

Les unités d'interface de module peuvent exporter des déclarations (y compris des définitions), qui peuvent être importées par d'autres unités de traduction. Pour exporter une déclaration, soit préfixez-la avec le mot-clé export , soit placez-la à l'intérieur d'un bloc export .

export déclaration
export { séquence-de-déclarations  (optionnel) }
export module A; // déclare l'unité d'interface de module primaire pour le module nommé 'A'
// hello() sera visible par les unités de traduction important 'A'
export char const* hello() { return "hello"; } 
// world() ne sera PAS visible.
char const* world() { return "world"; }
// one() et zero() seront tous deux visibles.
export
{
    int one()  { return 1; }
    int zero() { return 0; }
}
// L'exportation des espaces de noms fonctionne également : hi::english() et hi::french() seront visibles.
export namespace hi
{
    char const* english() { return "Hi!"; }
    char const* french()  { return "Salut!"; }
}

Importation de modules et d'unités d'en-tête

Les modules sont importés via une déclaration d'importation :

export (facultatif) import nom-du-module attr  (facultatif) ;

Toutes les déclarations et définitions exportées dans les unités d'interface de module du module nommé donné seront disponibles dans l'unité de traduction utilisant la déclaration d'importation.

Les déclarations d'importation peuvent être exportées dans une unité d'interface de module. C'est-à-dire que si le module B exporte-importe A , alors l'importation de B rendra également visibles toutes les exportations de A .

Dans les unités de module, toutes les déclarations d'importation (y compris les export-imports) doivent être regroupées après la déclaration du module et avant toutes les autres déclarations.

/////// A.cpp (unité d'interface de module primaire de 'A')
export module A;
export char const* hello() { return "hello"; }
/////// B.cpp (unité d'interface de module primaire de 'B')
export module B;
export import A;
export char const* world() { return "world"; }
/////// main.cpp (pas une unité de module)
#include <iostream>
import B;
int main()
{
    std::cout << hello() << ' ' << world() << '\n';
}

#include ne doit pas être utilisé dans une unité de module (en dehors de la fragment de module global ), car toutes les déclarations et définitions incluses seraient considérées comme faisant partie du module. À la place, les en-têtes peuvent également être importés comme unités d'en-tête avec une déclaration d'importation :

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

Un module d'en-tête est une unité de traduction distincte synthétisée à partir d'un en-tête. L'importation d'un module d'en-tête rendra accessibles toutes ses définitions et déclarations. Les macros du préprocesseur sont également accessibles (car les déclarations d'importation sont reconnues par le préprocesseur).

Cependant, contrairement à #include , les macros de prétraitement déjà définies au point de la déclaration d'importation n'affecteront pas le traitement de l'en-tête. Cela peut être gênant dans certains cas (certains en-têtes utilisent des macros de prétraitement comme forme de configuration), auquel cas l'utilisation du fragment de module global est nécessaire.

/////// A.cpp (unité d'interface de module primaire 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 (pas une unité de module)
import A;
int main()
{
    std::string_view message = "Hello, world!";
    print(message);
}

Fragment de module global

Les modules peuvent être précédés d'un fragment de module global , qui peut être utilisé pour inclure des en-têtes lorsque l'importation des en-têtes n'est pas possible (notamment lorsque l'en-tête utilise des macros de préprocesseur comme configuration).

module;

directives-de-prétraitement  (optionnel)

déclaration-de-module

Si une unité de module possède un fragment de module global, alors sa première déclaration doit être module; . Ensuite, seules les directives de préprocesseur peuvent apparaître dans le fragment de module global. Enfin, une déclaration de module standard marque la fin du fragment de module global et le début du contenu du module.

/////// A.cpp (unité d'interface de module primaire de 'A')
module;
// La définition de _POSIX_C_SOURCE ajoute des fonctions aux en-têtes standard,
// conformément à la norme POSIX.
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
export module A;
import <ctime>;
// Uniquement pour la démonstration (mauvaise source d'aléatoire).
// Utilisez <random> de C++ à la place.
export double weak_random()
{
    std::timespec ts;
    std::timespec_get(&ts, TIME_UTC); // de <ctime>
    // Fourni dans <stdlib.h> selon la norme POSIX.
    srand48(ts.tv_nsec);
    // drand48() retourne un nombre aléatoire entre 0 et 1.
    return drand48();
}
/////// main.cpp (pas une unité de module)
import <iostream>;
import A;
int main()
{
    std::cout << "Valeur aléatoire entre 0 et 1 : " << weak_random() << '\n';
}

Fragment de module privé

L'unité d'interface du module primaire peut être suffixée par un fragment de module privé , ce qui permet à un module d'être représenté comme une seule unité de traduction sans rendre tous les contenus du module accessibles aux importateurs.

module : private;

séquence-de-déclarations  (optionnel)

Fragment de module privé termine la partie de l'unité d'interface du module qui peut affecter le comportement d'autres unités de traduction. Si une unité de module contient un fragment de module privé , elle sera la seule unité de module de son module.

export module foo;
export int f();
module : private; // termine la partie de l'unité d'interface du module qui
                  // peut affecter le comportement d'autres unités de traduction
                  // démarre un fragment de module privé
int f()           // définition non accessible depuis les importateurs de foo
{
    return 42;
}

Partitions de module

Un module peut avoir des unités de partition de module . Ce sont des unités de module dont les déclarations de module incluent une partition de module, qui commence par un deux-points : et est placée après le nom du module.

export module A:B; // Déclare une unité d'interface de module pour le module 'A', partition ':B'.

Une partition de module représente exactement une unité de module (deux unités de module ne peuvent pas désigner la même partition de module). Elles sont visibles uniquement depuis l'intérieur du module nommé (les unités de traduction en dehors du module nommé ne peuvent pas importer directement une partition de module).

Une partition de module peut être importée par des unités de module du même module nommé.

export (optionnel) import module-partition attr  (optionnel) ;
/////// A-B.cpp   
export module A:B;
...
/////// A-C.cpp
module A:C;
...
/////// A.cpp
export module A;
import :C;
export import :B;
...
**Note:** Le code source C++ n'a pas été traduit conformément aux instructions, car il se trouve dans des balises `
` et contient des termes spécifiques au C++. Seuls les commentaires dans le code source auraient pu être traduits, mais ils sont purement informatifs (noms de fichiers) et n'ont donc pas été modifiés pour préserver la cohérence du code.

Toutes les définitions et déclarations dans une partition de module sont visibles par l'unité de module importatrice, qu'elles soient exportées ou non.

Les partitions de module peuvent être des unités d'interface de module (lorsque leurs déclarations de module contiennent export ). Elles doivent être export-importées par l'unité d'interface de module principale, et leurs déclarations exportées seront visibles lorsque le module est importé.

export (optionnel) import module-partition attr  (optionnel) ;
///////  A.cpp   
export module A;     // unité d'interface de module primaire
export import :B;    // Hello() est visible lors de l'importation de 'A'.
import :C;           // WorldImpl() est maintenant visible uniquement pour 'A.cpp'.
// export import :C; // ERREUR : Impossible d'exporter une unité d'implémentation de module.
// World() est visible par toute unité de traduction important 'A'.
export char const* World()
{
    return WorldImpl();
}
/////// A-B.cpp 
export module A:B; // unité d'interface de module partition
// Hello() est visible par toute unité de traduction important 'A'.
export char const* Hello() { return "Hello"; }
/////// A-C.cpp 
module A:C; // unité d'implémentation de module partition
// WorldImpl() est visible par toute unité de module de 'A' important ':C'.
char const* WorldImpl() { return "World"; }
/////// main.cpp 
import A;
import <iostream>;
int main()
{
    std::cout << Hello() << ' ' << World() << '\n';
    // WorldImpl(); // ERREUR : WorldImpl() n'est pas visible.
}

Propriété du module

En général, si une déclaration apparaît après la déclaration de module dans une unité de module, elle est attachée à ce module.

Si une déclaration d'une entité est attachée à un module nommé, cette entité ne peut être définie que dans ce module. Toutes les déclarations d'une telle entité doivent être attachées au même module.

Si une déclaration est attachée à un module nommé, et qu'elle n'est pas exportée, le nom déclaré possède module linkage .

export module lib_A;
int f() { return 0; } // f a une liaison de module
export int x = f();   // x est égal à 0
export module lib_B;
int f() { return 1; } // OK, f dans lib_A et f dans lib_B font référence à des entités différentes
export int y = f(); // y est égal à 1

Si deux déclarations d'une entité sont attachées à différents modules, le programme est mal formé ; aucun diagnostic n'est requis si aucune n'est accessible depuis l'autre.

/////// decls.h
int f(); // #1, attaché au module global
int g(); // #2, attaché au module global
/////// Interface du module M
module;
#include "decls.h"
export module M;
export using ::f; // OK, ne déclare pas une entité, exporte #1
int g();          // Erreur : correspond à #2, mais attaché à M
export int h();   // #3
export int k();   // #4
/////// Autre unité de traduction
import M;
static int h();   // Erreur : correspond à #3
int k();          // Erreur : correspond à #4

Les déclarations suivantes ne sont attachées à aucun module nommé (et ainsi l'entité déclarée peut être définie en dehors du module) :

export module lib_A;
namespace ns // ns n'est pas attaché à lib_A.
{
    export extern "C++" int f(); // f n'est pas attaché à lib_A.
           extern "C++" int g(); // g n'est pas attaché à lib_A.
    export              int h(); // h est attaché à lib_A.
}
// ns::h doit être défini dans lib_A, mais ns::f et ns::g peuvent être définis ailleurs (par exemple
// dans un fichier source traditionnel).

Notes

Macro de test de fonctionnalité Valeur Norme Fonctionnalité
__cpp_modules 201907L (C++20) Modules — prise en charge du langage de base
__cpp_lib_modules 202207L (C++23) Modules de la bibliothèque standard std et std. compat

Mots-clés

private , module , import , export

Rapports de défauts

Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.

DR Applicable à Comportement publié Comportement corrigé
CWG 2732 C++20 il n'était pas clair si les en-têtes importables peuvent
réagir à l'état du préprocesseur au point d'importation
aucune réaction
P3034R1 C++20 les noms de modules et les partitions de modules pouvaient
contenir des identifiants définis comme macros objet
interdit