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

ほとんどのC++プロジェクトは複数の翻訳単位を使用するため、それらの単位間で 宣言 定義 を共有する必要があります。この目的のために ヘッダー の使用が顕著であり、その例として 標準ライブラリ があり、その宣言は 対応するヘッダーをインクルードする ことで提供されます。

モジュールは、翻訳単位間で宣言と定義を共有するための言語機能です。 これらはヘッダーの一部のユースケースに対する代替手段となります。

モジュールは namespaces と直交する概念です。

// helloworld.cpp
export module helloworld; // モジュール宣言
import <iostream>;        // インポート宣言
export void hello()       // エクスポート宣言
{
    std::cout << "Hello world!\n";
}
// main.cpp
import helloworld; // import宣言
int main()
{
    hello();
}

目次

構文

export (オプション) module module-name module-partition  (オプション) attr  (オプション) ; (1)
export declaration (2)
export { declaration-seq  (オプション) } (3)
export (オプション) import module-name attr  (オプション) ; (4)
export (オプション) import module-partition attr  (オプション) ; (5)
export (オプション) import header-name attr  (オプション) ; (6)
module; (7)
module : private; (8)
1) モジュール宣言。現在の翻訳単位が モジュールユニット であることを宣言します。
2,3) エクスポート宣言。 declaration または declaration-seq 内のすべての名前空間スコープ宣言をエクスポートします。
4,5,6) インポート宣言。モジュールユニット/モジュールパーティション/ヘッダーユニットをインポートします。
7) グローバルモジュールフラグメントを 開始します

モジュール宣言

翻訳単位はモジュール宣言を持つことができ、その場合それは モジュール単位 と見なされます。 提供される モジュール宣言 は、翻訳単位の最初の宣言でなければなりません(後述する グローバルモジュールフラグメント を除く)。各モジュール単位は、モジュール宣言で提供される モジュール名 (およびオプションでパーティション)に関連付けられます。

export (オプション) module module-name module-partition  (オプション) attr  (オプション) ;

モジュール名は、ドットで区切られた1つ以上の識別子で構成されます(例: mymodule mymodule.mysubmodule mymodule2 ...)。ドット自体に固有の意味はありませんが、慣例的に階層構造を表現するために使用されます。

モジュール名またはモジュールパーティション内のいずれかの識別子が object-like macro として定義されている場合、プログラムは不適格となります。

名前付きモジュールは、同じモジュール名を持つモジュールユニットの集合です。

export キーワードを持つ宣言を含むモジュール単位は モジュールインターフェース単位 と呼ばれ、その他のモジュール単位は モジュール実装単位 と呼ばれます。

名前付きモジュールごとに、モジュールパーティションを指定しないモジュールインターフェース単位が厳密に1つ存在しなければならない。このモジュール単位は primary module interface unit と呼ばれる。そのエクスポート内容は、対応する名前付きモジュールをインポートする際に利用可能となる。

// (各行は個別の翻訳単位を表します)
export module A;   // 名前付きモジュール 'A' のプライマリモジュールインターフェース単位を宣言
module A;          // 名前付きモジュール 'A' のモジュール実装単位を宣言
module A;          // 名前付きモジュール 'A' の別のモジュール実装単位を宣言
export module A.B; // 名前付きモジュール 'A.B' のプライマリモジュールインターフェース単位を宣言
module A.B;        // 名前付きモジュール 'A.B' のモジュール実装単位を宣言

宣言と定義のエクスポート

モジュールインターフェース単位は、他の翻訳単位からインポート可能な宣言(定義を含む)をエクスポートできます。宣言をエクスポートするには、 export キーワードを前置するか、あるいは export ブロック内に配置します。

export 宣言
export { 宣言シーケンス  (オプション) }
export module A; // 名前付きモジュール 'A' のプライマリモジュールインターフェース単位を宣言
// hello() はモジュール 'A' をインポートする翻訳単位から可視となる
export char const* hello() { return "hello"; } 
// world() は可視とならない
char const* world() { return "world"; }
// one() と zero() の両方が可視となる
export
{
    int one()  { return 1; }
    int zero() { return 0; }
}
// 名前空間のエクスポートも機能する: hi::english() と hi::french() が可視となる
export namespace hi
{
    char const* english() { return "Hi!"; }
    char const* french()  { return "Salut!"; }
}

モジュールとヘッダーユニットのインポート

モジュールは import宣言 によってインポートされます:

export (オプション) import module-name attr  (オプション) ;

指定された名前付きモジュールのモジュールインターフェース単位でエクスポートされるすべての宣言と定義は、import宣言を使用する翻訳単位で利用可能になります。

モジュールインターフェース単位ではインポート宣言をエクスポートできます。つまり、モジュール B A をexport-importする場合、 B をインポートすると A からのすべてのエクスポートも可視になります。

モジュールユニットでは、すべてのインポート宣言(エクスポート-インポートを含む)は、モジュール宣言の後かつ他のすべての宣言の前にグループ化する必要があります。

/////// A.cpp (モジュール'A'のプライマリモジュールインターフェース単位)
export module A;
export char const* hello() { return "hello"; }
/////// B.cpp (モジュール'B'のプライマリモジュールインターフェース単位)
export module B;
export import A;
export char const* world() { return "world"; }
/////// main.cpp (モジュール単位ではない)
#include <iostream>
import B;
int main()
{
    std::cout << hello() << ' ' << world() << '\n';
}

#include はモジュール単位( global module fragment の外部)では使用すべきではありません。なぜなら、インクルードされたすべての宣言と定義がモジュールの一部と見なされるためです。代わりに、ヘッダーは header units として import declaration を使用してインポートすることもできます:

export (オプション) import header-name attr  (オプション) ;

ヘッダーユニットは、ヘッダーから合成された個別の翻訳単位です。ヘッダーユニットをインポートすると、そのすべての定義と宣言が利用可能になります。プリプロセッサマクロも利用可能です(インポート宣言はプリプロセッサによって認識されるため)。

しかしながら、 #include とは異なり、インポート宣言時点で既に定義されているプリプロセスマクロはヘッダーの処理に影響を与えません。これは場合によっては不便なことがあります(一部のヘッダーはプリプロセスマクロを設定の形式として使用しています)。そのような場合には global module fragment の使用が必要となります。

/////// A.cpp ('A'のプライマリモジュールインターフェースユニット)
export module A;
import <iostream>;
export import <string_view>;
export void print(std::string_view message)
{
    std::cout << message << std::endl;
}
/////// main.cpp (モジュールユニットではない)
import A;
int main()
{
    std::string_view message = "Hello, world!";
    print(message);
}

グローバルモジュールフラグメント

モジュール単位は global module fragment で始めることができ、これはヘッダーのインポートが不可能な場合(特にヘッダーが設定用のプリプロセスマクロを使用している場合)にヘッダーを含めるために使用できます。

module;

プリプロセッサ指令  (オプション)

モジュール宣言

モジュール単位がグローバルモジュールフラグメントを持つ場合、その最初の宣言は module; でなければなりません。その後、グローバルモジュールフラグメント内には プリプロセッサディレクティブ のみが記述できます。その後、標準モジュール宣言がグローバルモジュールフラグメントの終了とモジュール内容の開始を示します。

/////// A.cpp ('A'のプライマリモジュールインターフェースユニット)
module;
// _POSIX_C_SOURCEを定義すると、POSIX標準に従って標準ヘッダーに関数が追加されます
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
export module A;
import <ctime>;
// デモンストレーション専用(乱数の質が低い)
// 代わりにC++の<random>を使用してください
export double weak_random()
{
    std::timespec ts;
    std::timespec_get(&ts, TIME_UTC); // <ctime>から
    // POSIX標準に従って<stdlib.h>で提供されます
    srand48(ts.tv_nsec);
    // drand48()は0から1の間の乱数を返します
    return drand48();
}
/////// main.cpp (モジュールユニットではありません)
import <iostream>;
import A;
int main()
{
    std::cout << "0から1の間の乱数値: " << weak_random() << '\n';
}

プライベートモジュールフラグメント

プライマリモジュールインターフェースユニットは private module fragment で終了することができ、これによりモジュールを単一の翻訳単位として表現しながら、モジュールの内容すべてをインポーターがアクセス可能にすることなく実現できます。

module : private;

declaration-seq  (optional)

プライベートモジュールフラグメント は、他の翻訳単位の動作に影響を与える可能性があるモジュールインターフェース単位の部分を終了します。モジュール単位が プライベートモジュールフラグメント を含む場合、それはそのモジュールの唯一のモジュール単位となります。

export module foo;
export int f();
module : private; // 他の翻訳単位の動作に影響を与える可能性のある
                  // モジュールインターフェース単位の部分を終了
                  // プライベートモジュールフラグメントを開始
int f()           // fooのインポーターから到達できない定義
{
    return 42;
}

モジュール分割

モジュールは モジュールパーティション単位 を持つことができます。これらはモジュール宣言にモジュールパーティションを含むモジュール単位であり、モジュール名の後にコロン : で始まるパーティションが配置されます。

export module A:B; // モジュール 'A' のパーティション ':B' に対するモジュールインターフェースユニットを宣言します。

モジュールパーティションは正確に1つのモジュール単位を表します(2つのモジュール単位が同じモジュールパーティションを指定することはできません)。これらは名前付きモジュール内部からのみ可視です(名前付きモジュール外部の翻訳単位はモジュールパーティションを直接インポートできません)。

モジュールパーティションは、同じ名前のモジュールのモジュールユニットによってインポートできます。

export (オプション) import モジュールパーティション attr  (オプション) ;
/////// A-B.cpp   
export module A:B;
...
/////// A-C.cpp
module A:C;
...
/////// A.cpp
export module A;
import :C;
export import :B;
...

モジュールパーティション内のすべての定義と宣言は、エクスポートされているかどうかに関わらず、インポートするモジュールユニットから可視です。

モジュールパーティションは、モジュールインターフェースユニットになることができます(それらのモジュール宣言に export が含まれている場合)。これらはプライマリモジュールインターフェースユニットによってエクスポート-インポートされなければならず、エクスポートされた宣言はモジュールがインポートされたときに可視になります。

export (オプション) import モジュールパーティション attr  (オプション) ;
///////  A.cpp   
export module A;     // プライマリモジュールインターフェースユニット
export import :B;    // Hello()は'A'をインポートする際に可視となる
import :C;           // WorldImpl()は'A.cpp'のみで可視となる
// export import :C; // エラー: モジュール実装ユニットをエクスポートできない
// World()は'A'をインポートする任意の翻訳単位で可視となる
export char const* World()
{
    return WorldImpl();
}
/////// A-B.cpp 
export module A:B; // パーティションモジュールインターフェースユニット
// Hello()は'A'をインポートする任意の翻訳単位から可視です
export char const* Hello() { return "Hello"; }
/////// A-C.cpp 
module A:C; // パーティションモジュール実装ユニット
// WorldImpl()は':C'をインポートする'A'の任意のモジュールユニットから可視です
char const* WorldImpl() { return "World"; }
/////// main.cpp 
import A;
import <iostream>;
int main()
{
    std::cout << Hello() << ' ' << World() << '\n';
    // WorldImpl(); // エラー: WorldImpl()は可視ではありません。
}

モジュール所有権

一般的に、モジュール単位でモジュール宣言の後に宣言が現れる場合、それはそのモジュールに 付属する ことになります。

エンティティの宣言が名前付きモジュールに付属している場合、そのエンティティはそのモジュール内でのみ定義できます。そのようなエンティティのすべての宣言は、同じモジュールに付属している必要があります。

宣言が名前付きモジュールに付属しており、かつエクスポートされていない場合、宣言された名前は module linkage を持ちます。

export module lib_A;
int f() { return 0; } // fはモジュールリンケージを持つ
export int x = f();   // xは0に等しい
export module lib_B;
int f() { return 1; } // OK、lib_Aのfとlib_Bのfは異なるエンティティを参照する
export int y = f(); // yは1に等しい

あるエンティティの 2つの宣言 が異なるモジュールに属している場合、プログラムは不適格(ill-formed)となる。互いに到達可能でない場合、診断メッセージは要求されない。

/////// decls.h
int f(); // #1, グローバルモジュールに所属
int g(); // #2, グローバルモジュールに所属
/////// モジュールMのインターフェース
module;
#include "decls.h"
export module M;
export using ::f; // OK: エンティティを宣言せず、#1をエクスポート
int g();          // エラー: #2に一致するが、Mにアタッチされている
export int h();   // #3
export int k();   // #4
/////// 他の翻訳単位
import M;
static int h();   // エラー: #3に一致
int k();          // エラー: #4に一致

以下の宣言は名前付きモジュールに属していません(したがって、宣言されたエンティティはモジュール外で定義できます):

export module lib_A;
namespace ns // nsはlib_Aにアタッチされない
{
    export extern "C++" int f(); // fはlib_Aにアタッチされない
           extern "C++" int g(); // gはlib_Aにアタッチされない
    export              int h(); // hはlib_Aにアタッチされる
}
// ns::hはlib_A内で定義されなければならないが、ns::fとns::gは他の場所(例えば
// 従来のソースファイル内)で定義可能

注記

機能テスト マクロ 標準 機能
__cpp_modules 201907L (C++20) モジュール — コア言語サポート
__cpp_lib_modules 202207L (C++23) 標準ライブラリモジュール std および std. compat

キーワード

private module import export

不具合報告

以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。

DR 適用対象 公開時の動作 正しい動作
CWG 2732 C++20 インポート可能ヘッダーがインポート時点の
プリプロセッサ状態に反応するか不明確だった
反応しない
P3034R1 C++20 モジュール名とモジュールパーティションに
オブジェクト形式マクロとして定義された識別子を含められた
禁止