
On Sun, 22 Dec 2024 at 16:45, Peter Dimov
Ruben Perez wrote:
By the way, I posted about the optional `export` idea to the committee mailing list, and Michael Spencer replied and basically said "don't".
"My suggestion is to do either `export import
;` in the module if you are ok with getting extra declarations, or `export using ...` like libc++ does. Trying to textually include a header into the purview of a named module is fraught with issues." Since everything that's textually included becomes attached to the named module, it's too easy to create ODR violations.
But, apparently, Clang doesn't like header units, so the above is also not quite optimal.
I'd like to understand more about this, since this is the approach I have been following (and AFAIK, the one that John Maddock and Matt Borland followed, too). What would be the possible ODR violations here? Does he refer to the case where we forget to #ifdef-out an include for a dependency?
Yes. Assume for a moment that we don't have `import std;` available. In that case, an #include <algorithm> from one of our headers would attach all its declarations and definitions to our named module. When a user imports our module and includes <algorithm>, that's an ODR violation.
The way to avoid this is to include <algorithm>, along with everything else, in the GMF of our named module, so that the includes inside the module become no-ops. But we could easily miss such an include.
I see. I don't know if it's the same thing, but this looks similar to what happens when you mix imports and includes for the standard library, at least in MSVC. I know it's not supposed to be like this, but it is (at least now). I wonder whether there is an (automated) way to check for these, as it's definitely error-prone. My guess is that once export using works well, we can replace what I'm writing by export using (so we gain resilience), while keeping all the conditional includes I've introduced (to reduce overhead). So it wouldn't be wasted work, after all.
This would also apply to Boost libraries that aren't modules, such as Config.
This problem seems to make modules an all or nothing proposition. If one Boost library is a module, everything else also must be. We can't really mix and match modules and includes on a per-library basis as I envisaged. And things would be simplified considerably if we can also extend this to the standard library. It's either both import boost.* and import std, or neither.
What I've written until now assumes exactly this. It may be a lot to assume, but if you do need to mix, you can just use headers. I may be completely wrong, and mixing includes and imports will become important in the future. It felt fair to make this assumption (at least now) because the standard library seems to do it, too.
It looks like `export import
;` could be done easily by users (if it worked). At the source level yes, but not on the CMake level.
At the minute, `export using` has problems: 1) It doesn't work properly under MSVC because of an apparent language bug regarding specializations [2] [3]. 2) It ends up placing *a lot* of declarations in the global module fragment. clang docs suggest to avoid this [1], and I recall someone on the ML argued in this direction, too, but I can't find the relevant message.
IIUC, this is still better for performance than mixing named modules and includes, even if the extern "C++" trick is used to avoid the ODR violations. And I'm not sure that we can realistically avoid mixing imports and includes.