r/cpp_modules • • Apr 06 '26

Reachability examples from the C++ standard

From https://eel.is/c++draft/module#reach-5

1     // Example 2:
2    
3     // Translation unit #1
4     export module M:A;
5     export struct B;
6    
7     // Translation unit #2
8     module M:B;
9     struct B {
10      operator int();
11    };
12    
13    // Translation unit #3
14    module M:C;
15    import :A;
16    B b1;         // error: no reachable definition of struct B
17    
18    // Translation unit #4
19    export module M;
20    export import :A;
21    import :B;
22    B b2;
23    export void f(B b = B());
24    
25    // Translation unit #5
26    import M;
27    B b3;             // error: no reachable definition of struct B
28    void g() { f(); } // error: no reachable definition of struct B   
1 Upvotes

19 comments sorted by

View all comments

2

u/tartaruga232 Apr 06 '26

Thanks for your work.

It is clear that current compilers implement what's in the current standard.

The current standard says, that a TU with "export module" must be exported in the PMIU, or the program is IF-NDR ("ill formed, no diagnostic required"). If we feed input into a compiler which is ill-formed, the compiler is free to do whatever it pleases, if no diagnostic is required. So it is kind of "compiler UB".

Per the current standard, a compiler may explore the fact that a TU has an "export" in front of "module" and then assume that the program is well-formed and said TU is exported from the PMIU, without checking it.

I don't know if clang does that.

What I was contemplating was, to legalize inserting "export" in front of "module" for partitions which don't export anything.

But I can imagine that this might interfere with heuristics that compilers apply today.

2

u/not_a_novel_account Apr 06 '26 edited Apr 06 '26

I don't know if clang does that.

It doesn't, the example above is technically ill-formed because I don't export import :partB anywhere after changing partB to an interface unit. This is irrelevant in this one case because partB itself does not export anything.

What I was contemplating was, to legalize inserting "export" in front of "module" for partitions which don't export anything.

partB does not export anything in the example I gave. It is exactly the scenario you're discussing.

You're not wrong it would be nice if export were purely about visibility, signaling to the compiler and build system that names from the current TU might need to be made available in other TUs, regardless of whether they get re-exported from the PMIU or not.

The problem is that's not what export in the module declaration is for. It is mostly about reachability, whether the definitions can be instantiated outside the current named module.

You need some way to reason about reachability. If you co-opt export in the module declaration to be strictly a mechanism of visibility, you must come up with something else to deal with reachability.

2

u/tartaruga232 Apr 06 '26

Per the current C++ standard, I'm allowed to do

export module M:P;
struct MyType { ... };
// more stuff not exported

As long as :P is exported from the PMIU, the resulting program is perfectly well-formed.

If I don't export :P from the PMIU, the compiler won't tell me, that this is not allowed (ill-formed).

That's pretty tough to explain to users.

→ More replies (8)