1. Effects of This Paper
Note: In all of the examples in this paper it is assumed that
translation does not occur.
Status Quo | This Paper |
---|---|
|
|
2. The Problem
[basic.start.dynamic] states:
Dynamic initialization of non-local variables
and
V with static storage duration are ordered as follows:
W
If
and
V have ordered initialization and
W is defined before
V within a single translation unit, the initialization of
W is sequenced before the initialization of
V .
W If
has partially-ordered initialization,
V does not have unordered initialization, and
W is defined before
V in every translation unit in which
W is defined, then
W
if the program starts a thread ([intro.multithread]) other than the main thread ([basic.start.main]), the initialization of
strongly happens before the initialization of
V ;
W otherwise, the initialization of
is sequenced before the initialization of
V .
W Otherwise, if the program starts a thread other than the main thread before either
or
V is initialized, it is unspecified in which threads the initializations of
W and
V occur; the initializations are unsequenced if they occur in the same thread.
W Otherwise, the initializations of
and
V are indeterminately sequenced.
W
With textual
s the first rule holds for global initaliziers, but this
breaks when moving to
, either explicitly or via
translation. This happens
because header units are their own translation units, and thus fall into the last
rule. A similar problem exists with module interface units which transitively
or
such a header.
3. The Solution
Clang already ran into this issue and has a simple fix. Run all global initaliziers from translation units which are transitively imported when they are imported.
// H1.h inline int a = init (); // H2.h inline int b = init (); // TU.cpp int c = init (); import "H1.h" ; import "H2.h" ;
Clang modules tried to mimic includes as closely as possible, thus in the above
example Clang will initialize
first, and then
before
. If you
flip the order of the imports, then it would initialize
before
. While
this is a reasonable model for Clang modules, which was intended to mimic
s, it is a poor fit for C++20 modules, as it violates the rule that
the order of imports doesn’t matter.
This paper explores a simpler user model based on dependency order. All global initaliziers from
translation units which are transitively imported are run before any globals in
the translation unit which imports them. For the above example it would mean
that the initialization of
and
is sequenced before the initialization of
, but that the initializations of
and
are indeterminately sequenced.
3.1. The Catch
Initializers of inline variable
and variable
are ordered when "
is
defined before
in every translation unit in which
is defined." If the
above solution is applied blindly, then cycles can be created.
// H1.h int external ( int ); // H2.h #include "H1.h"inline int a = external ( 0 ); // H3.h #include "H1.h"inline int b = external ( 1 ); // M1.cppm module ; #include "H2.h"export module M1 ; import "H3.h" ; // M2.cppm module ; #include "H3.h"export module M2 ; import "H2.h" ;
This example has the following sequenced before graph:
M1 has an interface dependency on the the header unit "H3.h",
and contains a definition of
, thus
s initialization is sequenced before
's initialization. However; M2 has an interface dependency on the header
unit "M2.h" and contains a definition of
, thus
s initialization is
sequenced before
's initialization. This is a contradiction.
The ordering guarantees will need to be weakened for this case.
4. Implementation Units?
If we’re defining an ordering for interfaces, why not also define one for module implementation units and solve the init order problem once and for all? While there are plausible ways to define an ordering for implementation units, there are several reasons why this direction was not chosen at this time:
-
At best it can only order between different modules, not between implementation units of a single module.
-
Implementation units can form circular dependencies between modules.
-
It has not been implemented, and has an unknown runtime impact, potentially requring adding a global constructor to every module.
-
It is not required to fix the
issue, and thus out of scope for C++20.< iostream >
5. Ship Vehicle
This paper is targeting C++20 as the standard library is subtly broken without it.
6. Proposed Polls
-
Pick a model. In the graphs that follow a directed arrow represents a sequenced before relationship. Initializers with no forward path between them are indeterminately sequenced.
| ||
Current Clang Model | Relaxed Clang Model | Dependency Order Model |
---|---|---|
|
|
|
-
Forward P1874 to CWG for C++20.
7. Wording
The following wording assumes that the issue with interface dependency not applying to header units is fixed.
7.1. [basic.start.dynamic]
2 Dynamic initialization of non-local variablesand
V with static storage duration are ordered as follows:
W
If
and
V have ordered initialization and
W is defined before
V within a single translation unit , or
W is in a translation unit on which
V 's translation unit has an interface dependency , the initialization of
W is sequenced before the initialization of
V .
W If
has partially-ordered initialization,
V does not have unordered initialization, and
W is
V defined beforereachable fromin every translation unit in which
W is defined, then
W I believe that reachability is the correct thing to use in this case. It covers both "defined before" and interface dependency. It is not used for the previous case because it is slightly weaker, as it matters where the import occurs.
if the program starts a thread ([intro.multithread]) other than the main thread ([basic.start.main]), the initialization of
strongly happens before the initialization of
V ;
W otherwise, the initialization of
is sequenced before the initialization of
V .
W Otherwise, if the program starts a thread other than the main thread before either
or
V is initialized, it is unspecified in which threads the initializations of
W and
V occur; the initializations are unsequenced if they occur in the same thread.
W Otherwise, the initializations of
and
V are indeterminately sequenced.
W [ Note: This definition permits initialization of a sequence of ordered variables concurrently with another sequence. — end note ]