Skip to content

Commit c8fab88

Browse files
authored
[docs] [C++20] [Modules] Ideas for transitioning to modules (#80687)
This patch tries to provide some ideas to transform an existing libraries to modules. I feel this is helpful for users who is interested in modules from my observation. While the syntax of modules look easy to understand, the practice gets harder if the users want to make compatible work with headers. Especially the `std` module can be installed in clang18, I think such document may be helpful. I tried to not be too wordy in this document and I don't want the users to have impressions that they have to follow this or this is the best practice. So I tried to use the term `idea` imply the users this is not compulsory. I add some regular reviewers for modules, but review opinions from users are highly recommended. I want to land this before the releasing of clang18 (in the early March). We can and should improve this continuously.
1 parent 0a6c74e commit c8fab88

File tree

1 file changed

+339
-0
lines changed

1 file changed

+339
-0
lines changed

clang/docs/StandardCPlusPlusModules.rst

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,345 @@ the following style significantly:
610610

611611
The key part of the tip is to reduce the duplications from the text includes.
612612

613+
Ideas for converting to modules
614+
-------------------------------
615+
616+
For new libraries, we encourage them to use modules completely from day one if possible.
617+
This will be pretty helpful to make the whole ecosystems to get ready.
618+
619+
For many existing libraries, it may be a breaking change to refactor themselves
620+
into modules completely. So that many existing libraries need to provide headers and module
621+
interfaces for a while to not break existing users.
622+
Here we provide some ideas to ease the transition process for existing libraries.
623+
**Note that the this section is only about helping ideas instead of requirement from clang**.
624+
625+
Let's start with the case that there is no dependency or no dependent libraries providing
626+
modules for your library.
627+
628+
ABI non-breaking styles
629+
~~~~~~~~~~~~~~~~~~~~~~~
630+
631+
export-using style
632+
^^^^^^^^^^^^^^^^^^
633+
634+
.. code-block:: c++
635+
636+
module;
637+
#include "header_1.h"
638+
#include "header_2.h"
639+
...
640+
#include "header_n.h"
641+
export module your_library;
642+
export namespace your_namespace {
643+
using decl_1;
644+
using decl_2;
645+
...
646+
using decl_n;
647+
}
648+
649+
As the example shows, you need to include all the headers containing declarations needs
650+
to be exported and `using` such declarations in an `export` block. Then, basically,
651+
we're done.
652+
653+
export extern-C++ style
654+
^^^^^^^^^^^^^^^^^^^^^^^
655+
656+
.. code-block:: c++
657+
658+
module;
659+
#include "third_party/A/headers.h"
660+
#include "third_party/B/headers.h"
661+
...
662+
#include "third_party/Z/headers.h"
663+
export module your_library;
664+
#define IN_MODULE_INTERFACE
665+
extern "C++" {
666+
#include "header_1.h"
667+
#include "header_2.h"
668+
...
669+
#include "header_n.h"
670+
}
671+
672+
Then in your headers (from ``header_1.h`` to ``header_n.h``), you need to define the macro:
673+
674+
.. code-block:: c++
675+
676+
#ifdef IN_MODULE_INTERFACE
677+
#define EXPORT export
678+
#else
679+
#define EXPORT
680+
#endif
681+
682+
And you should put ``EXPORT`` to the beginning of the declarations you want to export.
683+
684+
Also it is suggested to refactor your headers to include thirdparty headers conditionally:
685+
686+
.. code-block:: c++
687+
688+
#ifndef IN_MODULE_INTERFACE
689+
#include "third_party/A/headers.h"
690+
#endif
691+
692+
#include "header_x.h"
693+
694+
...
695+
696+
This may be helpful to get better diagnostic messages if you forgot to update your module
697+
interface unit file during maintaining.
698+
699+
The reasoning for the practice is that the declarations in the language linkage are considered
700+
to be attached to the global module. So the ABI of your library in the modular version
701+
wouldn't change.
702+
703+
While this style looks not as convenient as the export-using style, it is easier to convert
704+
to other styles.
705+
706+
ABI breaking style
707+
~~~~~~~~~~~~~~~~~~
708+
709+
The term ``ABI breaking`` sounds terrifying generally. But you may want it here if you want
710+
to force your users to introduce your library in a consistent way. E.g., they either include
711+
your headers all the way or import your modules all the way.
712+
The style prevents the users to include your headers and import your modules at the same time
713+
in the same repo.
714+
715+
The pattern for ABI breaking style is similar with export extern-C++ style.
716+
717+
.. code-block:: c++
718+
719+
module;
720+
#include "third_party/A/headers.h"
721+
#include "third_party/B/headers.h"
722+
...
723+
#include "third_party/Z/headers.h"
724+
export module your_library;
725+
#define IN_MODULE_INTERFACE
726+
#include "header_1.h"
727+
#include "header_2.h"
728+
...
729+
#include "header_n.h"
730+
731+
#if the number of .cpp files in your project are small
732+
module :private;
733+
#include "source_1.cpp"
734+
#include "source_2.cpp"
735+
...
736+
#include "source_n.cpp"
737+
#else // the number of .cpp files in your project are a lot
738+
// Using all the declarations from thirdparty libraries which are
739+
// used in the .cpp files.
740+
namespace third_party_namespace {
741+
using third_party_decl_used_in_cpp_1;
742+
using third_party_decl_used_in_cpp_2;
743+
...
744+
using third_party_decl_used_in_cpp_n;
745+
}
746+
#endif
747+
748+
(And add `EXPORT` and conditional include to the headers as suggested in the export
749+
extern-C++ style section)
750+
751+
Remember that the ABI get changed and we need to compile our source files into the
752+
new ABI format. This is the job of the additional part of the interface unit:
753+
754+
.. code-block:: c++
755+
756+
#if the number of .cpp files in your project are small
757+
module :private;
758+
#include "source_1.cpp"
759+
#include "source_2.cpp"
760+
...
761+
#include "source_n.cpp"
762+
#else // the number of .cpp files in your project are a lot
763+
// Using all the declarations from thirdparty libraries which are
764+
// used in the .cpp files.
765+
namespace third_party_namespace {
766+
using third_party_decl_used_in_cpp_1;
767+
using third_party_decl_used_in_cpp_2;
768+
...
769+
using third_party_decl_used_in_cpp_n;
770+
}
771+
#endif
772+
773+
In case the number of your source files are small, we may put everything in the private
774+
module fragment directly. (it is suggested to add conditional include to the source
775+
files too). But it will make the compilation of the module interface unit to be slow
776+
when the number of the source files are not small enough.
777+
778+
**Note that the private module fragment can only be in the primary module interface unit
779+
and the primary module interface unit containing private module fragment should be the only
780+
module unit of the corresponding module.**
781+
782+
In that case, you need to convert your source files (.cpp files) to module implementation units:
783+
784+
.. code-block:: c++
785+
786+
#ifndef IN_MODULE_INTERFACE
787+
// List all the includes here.
788+
#include "third_party/A/headers.h"
789+
...
790+
#include "header.h"
791+
#endif
792+
793+
module your_library;
794+
795+
// Following off should be unchanged.
796+
...
797+
798+
The module implementation unit will import the primary module implicitly.
799+
We don't include any headers in the module implementation units
800+
here since we want to avoid duplicated declarations between translation units.
801+
This is the reason why we add non-exported using declarations from the third
802+
party libraries in the primary module interface unit.
803+
804+
And if you provide your library as ``libyour_library.so``, you probably need to
805+
provide a modular one ``libyour_library_modules.so`` since you changed the ABI.
806+
807+
What if there are headers only inclued by the source files
808+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
809+
810+
The above practice may be problematic if there are headers only included by the source
811+
files. If you're using private module fragment, you may solve the issue by including them
812+
in the private module fragment. While it is OK to solve it by including the implementation
813+
headers in the module purview if you're using implementation module units, it may be
814+
suboptimal since the primary module interface units now containing entities not belongs
815+
to the interface.
816+
817+
If you're a perfectionist, maybe you can improve it by introducing internal module partition unit.
818+
819+
The internal module partition unit is an importable module unit which is internal
820+
to the module itself. The concept just meets the headers only included by the source files.
821+
822+
We don't show code snippet since it may be too verbose or not good or not general.
823+
But it may not be too hard if you can understand the points of the section.
824+
825+
Providing a header to skip parsing redundant headers
826+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
827+
828+
It is a problem for clang to handle redeclarations between translation units.
829+
Also there is a long standing issue in clang (`problematic include after import <https://github.com/llvm/llvm-project/issues/61465>`_).
830+
But even if the issue get fixed in clang someday, the users may still get slower compilation speed
831+
and larger BMI size. So it is suggested to not include headers after importing the corresponding
832+
library.
833+
834+
However, it is not easy for users if your library are included by other dependencies.
835+
836+
So the users may have to write codes like:
837+
838+
.. code-block:: c++
839+
840+
#include "third_party/A.h" // #include "your_library/a_header.h"
841+
import your_library;
842+
843+
or
844+
845+
.. code-block:: c++
846+
847+
import your_library;
848+
#include "third_party/A.h" // #include "your_library/a_header.h"
849+
850+
For such cases, we suggest the libraries providing modules and the headers at the same time
851+
to provide a header to skip parsing all the headers in your libraries. So the users can
852+
import your library as the following style to skip redundant handling:
853+
854+
.. code-block:: c++
855+
856+
import your_library;
857+
#include "your_library_imported.h"
858+
#include "third_party/A.h" // #include "your_library/a_header.h" but got skipped
859+
860+
The implementation of ``your_library_imported.h`` can be a set of controlling macros or
861+
an overall controlling macro if you're using `#pragma once`. So you can convert your
862+
headers to:
863+
864+
.. code-block:: c++
865+
866+
#pragma once
867+
#ifndef YOUR_LIBRARY_IMPORTED
868+
...
869+
#endif
870+
871+
Importing modules
872+
~~~~~~~~~~~~~~~~~
873+
874+
When there are dependent libraries providing modules, we suggest you to import that in
875+
your module.
876+
877+
Most of the existing libraries would fall into this catagory once the std module gets available.
878+
879+
All dependent libraries providing modules
880+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
881+
882+
Life gets easier if all the dependent libraries providing modules.
883+
884+
You need to convert your headers to include thirdparty headers conditionally.
885+
886+
Then for export-using style:
887+
888+
.. code-block:: c++
889+
890+
module;
891+
import modules_from_third_party;
892+
#define IN_MODULE_INTERFACE
893+
#include "header_1.h"
894+
#include "header_2.h"
895+
...
896+
#include "header_n.h"
897+
export module your_library;
898+
export namespace your_namespace {
899+
using decl_1;
900+
using decl_2;
901+
...
902+
using decl_n;
903+
}
904+
905+
For export extern-C++ style:
906+
907+
.. code-block:: c++
908+
909+
export module your_library;
910+
import modules_from_third_party;
911+
#define IN_MODULE_INTERFACE
912+
extern "C++" {
913+
#include "header_1.h"
914+
#include "header_2.h"
915+
...
916+
#include "header_n.h"
917+
}
918+
919+
For ABI breaking style,
920+
921+
.. code-block:: c++
922+
923+
export module your_library;
924+
import modules_from_third_party;
925+
#define IN_MODULE_INTERFACE
926+
#include "header_1.h"
927+
#include "header_2.h"
928+
...
929+
#include "header_n.h"
930+
931+
#if the number of .cpp files in your project are small
932+
module :private;
933+
#include "source_1.cpp"
934+
#include "source_2.cpp"
935+
...
936+
#include "source_n.cpp"
937+
#endif
938+
939+
We don't need the non-exported using declarations if we're using implementation module
940+
units now. We can import thirdparty modules directly in the implementation module
941+
units.
942+
943+
Partial dependent libraries providing modules
944+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
945+
946+
In this case, we have to mix the use of ``include`` and ``import`` in the module of our
947+
library. The key point here is still to remove duplicated declarations in translation
948+
units as much as possible. If the imported modules provide headers to skip parsing their
949+
headers, we should include that after the including. If the imported modules don't provide
950+
the headers, we can make it ourselves if we still want to optimize it.
951+
613952
Known Problems
614953
--------------
615954

0 commit comments

Comments
 (0)