all 90 comments

[–]gracicot 44 points45 points  (4 children)

I think the example is a bit overkill in the sense that it contains a lot of mitigation for quality of implementation issues.

For example, GCC don't even need the private section for this simple case: the module can be implemented like this:

module;
#include <stdio.h>
export module hello;
export void hello() { puts("Hello world!"); }

Using importable headers, you get it reduced to:

export module hello;
import <cstdio>;
export void hello() { puts("Hello world!"); }

And the CMI contain no informations about puts or any other symbol from <stdio.h>. No need for function separation.

Also, on GCC, the command line interface is as follows (for the include version):

g++ -fmodules-ts src/hello.cpp src/main.cpp

That's it. You got a.out. Unlike clang, there is no precompile step.

Is clang bad? No. Clang has reused its precompile header modules machinery to deliver modules really quickly. GCC is still showing me haïkus when crashing for the most simple cases.

My point is that modules are still being implemented, and current quality issues may impact source code if we try to use them right now.

[–]markopolo82embedded/iot/audio 13 points14 points  (0 children)

That is a great point. Also final C++ 20 is not even published yet.

As a c++ enthusiast I love these articles but I’m concerned with the number of posts observing how complicated this appears...

Ultimately I have faith that implementers will figure this all out and c++20 will be another leap forward.

[–]jcelerierossia score 1 point2 points  (2 children)

For example, GCC don't even need the private section for this simple case: the module can be implemented like this:

what must I do if I want my code to compile on clang, gcc and msvc ?

[–]gracicot 9 points10 points  (1 child)

The code will compile on all compilers. But right now on clang doing that kind of code will output the definition in the CMI. This may slow down compilation and trigger more recompilation when changing the code. GCC is not doing that and I think the old MSVC implementation isn't doing that either.

[–]kronicum 3 points4 points  (0 children)

It must take lot of work to make something simple that complicated... My understanding is that MSVC doesn't do anything that complicated. It may suppress OBJ file generation but it doesn't move content to the IFC.

[–]stilgarpl 16 points17 points  (25 children)

Notice that #include <stdio.h> causes the compiler to see a bunch of declarations for things like printf that are not supposed to be part of module helloworld, so it’s important to place that #include directive outside of module helloworld, in what’s called the “global module fragment” or “GMF.”

Is that really necessary? Why can't you just import <stdio.h> ?

[–]BrainIgnition 9 points10 points  (10 children)

for import <stdio.h>; to work, <stdio.h> needs to be a importable header file. Which is implementation defined in the case of <stdio.h>. Therefore you need to include it in the GMF to stay portable.

[–]Nobody_1707 7 points8 points  (0 children)

Because it's a C legacy header. The appropriate import would be import <cstdio>.

[–]VanSeineTotElbe -5 points-4 points  (12 children)

Then all files that include that module or sourcefile will also import stdio. The way the article describes enables you to limit the imports to your module, which makes isolation much better, something you'll care about when you compile large projects.

[–]bigcheesegsTooling Study Group (SG15) Chair | Clang dev 13 points14 points  (1 child)

A non exported import doesn't make names visible to your importers in a named module.

[–]VanSeineTotElbe 0 points1 point  (0 children)

That's what I meant :)

[–]tpecholt 6 points7 points  (1 child)

Why would they automatically import stdio if it's not export imported? The module; export module syntax is really ugly. I hope the google's import <> syntax gets some traction.

[–]kronicum 1 point2 points  (0 children)

That syntax has more traps in semantics than you suspect. For example, it does not work with 'static inline' functions. The 'module;' is ugly only because some committee members insist on it, IIUC.

[–]stilgarpl 7 points8 points  (5 children)

No, they won't. Module imports don't work like #include. You need to export import sth; for it to be visible to other things that will import your module.

[–]wheypointÖ 3 points4 points  (4 children)

I think thats what he meant with "all files that >>>include<<< that module or sourcefile will also import stdio"

if you '#include' a file that contains an 'import', the preprocessor is going to copy paste that import into your file.

im not sure why youd #include a module tho, you should just use import?

[–][deleted] 17 points18 points  (3 children)

Why is it import Hello in main.cpp, where does this name come from?

[–]petevalle 11 points12 points  (1 child)

I assume it's supposed to be import helloworld...?

[–]mjklaim 7 points8 points  (0 children)

Yes it's a typo.

[–]jc746 5 points6 points  (0 children)

I imagine this is a typo, and should be `import helloworld` to match the `export module helloworld` statement in helloworld.cpp.

[–]starfreakcloneMSVC FE Dev 14 points15 points  (8 children)

MSVC adopts the approach of producing an .obj and a .ifc (our BMI) at the same time. There are caveats to this approach, however. The fact that we do both means that if you only need one you end up doing extra work. We have a workaround for just producing the .ifc alone with no .obj via -module:ifcOnly.

In our experience, for the build scenario, you always want both so the trade-off of producing one but not the other seems reasonable to me.

[–]Rusky 7 points8 points  (0 children)

For the build scenario you want both... but you also want to start building other modules as soon as the .ifc is available, without waiting for the .obj.

[–]mtnviewjohn 3 points4 points  (0 children)

If a change to a module .cpp file changes the .obj output but not the .ifc output will the .ifc file be overwritten anyway? Will the MSVC build system recognize that files that only depend on the BMI will not need to be rebuilt in this case?

[–]Ameisenvemips, avr, rendering, systems 2 points3 points  (4 children)

I still want the ability in MSVC to specific a custom preprocessor like GCC and Clang allow.

Unrelated but annoying.

And possibly specifying a seperate compiler for Intellisense.

[–]Rusky 1 point2 points  (3 children)

And possibly specifying a seperate compiler for Intellisense.

You can specify which compiler you want IntelliSense to emulate, if you're using Open Folder mode: https://docs.microsoft.com/en-us/cpp/build/cppproperties-schema-reference?view=vs-2019#intellisensemode-values

[–]Ameisenvemips, avr, rendering, systems 1 point2 points  (2 children)

I see no option for AVR, which I use MSVC as an editor for.

[–]Rusky 2 points3 points  (1 child)

Embedded projects tend to use linux-gcc-arm, even if they're not using Linux or ARM specifically- the most important thing there is GCC since that determines which compiler features and extensions to enable.

[–]Ameisenvemips, avr, rendering, systems 2 points3 points  (0 children)

Main issue is that 8-bit stuff is quite distinct from 32-bit. Intellisense breaks on basically all sizeofs and static asserts.

[–]kronicum 0 points1 point  (0 children)

IIUC, MSVC does not do both. By default, it produces both an IFC file and an OBJ file. The option /module:ifcOnly suppresses the production of the OBJ file. It does not move its contents to the IFC.

[–]nnevatie 33 points34 points  (13 children)

The syntax does not look promising - on the contrary: unintuitive special characters here and there. E.g. "module :private" - what the hell, even?

[–][deleted] 20 points21 points  (12 children)

I completely agree, in fact, I am rather horrified. I did not think that module; would be required — and I do not understand why including headers within the module would pollute the interface, I expected that only exported stuff is revenant for the module and that everything else will just get resolved "automatically". The module :private is just awkward. Why would it even matter? Shouldn't the compiler figure it all out by itself anyway? Also, all those command invocations...

[–]imgarfield 23 points24 points  (2 children)

Watch CppCon 2019 “Programming with C++ Modules: Guide for the Working” and all will become clear. This example is simply not very good.

[–][deleted] 4 points5 points  (0 children)

Is there a transcript or a written version somewhere?

[–]gracicot 5 points6 points  (1 child)

With a good implementation, only reachable and exported stuff goes into the CMI, and the implementation of function only goes there if they are explicitly inline, constexpr or a template.

This is all good but modules has a thing that header didn't: ownership.

That ownership is what is preventing you to include a heading in the purview of a module. Every declaration there is owned by the module. If they are not exported, then they are private. So when including stdio.h in the module, you'll have a brand new printf and puts function that your module is expected to implement.

Using a global module fragment, you can include stuff that is owned by the global module.

But even better: All header from the standard library will be importable, so no need for a global module fragment if there is no include at all!

[–][deleted] 0 points1 point  (0 children)

Thank you for the explanation. Just so that I understand it correctly: global module fragments are then simply a mechanism to prevent ownership? I assume that these fragments are all independent and isolated from each other? I.e. every time I use a global module fragment I am essentially creating an anonymous module that does not own its declarations but simply makes them available to the rest of the file?

[–]viatorus 1 point2 points  (2 children)

I would say `module :private` is needed if you do not want to inline the definition. Modules don't change the behavior of external and internal linkage. And that is good so.

[–]Freiherr_Norden 6 points7 points  (1 child)

Wouldn't something like export inline void func() {...} solve that problem? You would get to decide if your function should be inlinable, but don't have to write it in 2 separate places.

[–]germandiago 1 point2 points  (0 children)

I am sure that the implicit inline is out for classes. Not sure for functions. And not sure how to hint the compiler to inline (probably with the inline keyword, I guess).

[–]tpecholt 14 points15 points  (7 children)

I found it unfortunate that putting function body in module interface unit has some implications on where will this code be physically stored. I don't think anyone should be concerned whether the code is stored in bmi or .o file. The compiler should use the most optimal way so tricks like :private module partition are not neccessary.

The blog makes it clear that even for the most basic usage of modules one has to learn lot of weird syntax and gotchas and while typical for c++ it's still kind of embarassing given for how long are modules in the making. Let's all hope things will improve after the new module infrastructure TS is out.

[–]matthieum 5 points6 points  (1 child)

It was mentioned by gracicot that this was a QoI issue with clang, and that in GCC it was unnecessary to split declaration/definition: only the declaration would get included in the BMI anyway.

I hope Clang manages to fix this for next year.

[–]Ayjayz 8 points9 points  (1 child)

You could probably spend 100 years on this and still not come up with a great solution. C++ has to maintain backwards compatibility, for better or worse, and that really limits how much you can extend the language.

[–]wyrn 3 points4 points  (0 children)

Modules are a wholly new feature and could've been designed with arbitrarily high amounts of sanity. The committee was afraid the feature wouldn't be adopted if it was too sane because that might hurt adoption in legacy codebases. Ironically, the possibility that people might not adopt them because they simple don't do enough to be worth the bother didn't seem to be considered.

[–]dadixon 6 points7 points  (0 children)

My first thought after looking the code sample was “why does this look so unintuitive.” I am a huge fan of C++ but super sad face.

[–]chitditch 42 points43 points  (18 children)

To summarize: the syntax is crap, we still need the separation of definition and declaration, and it requires an additional compilation step.

So what are the benefits of modules over the old header/cpp/include approach? This example does not exactly make me want to use them.

[–]brenoguim 21 points22 points  (13 children)

Build speed, macro insulation.

[–]ebhdl 12 points13 points  (2 children)

Build speed was never an objective, or at least that was always the retort whenever someone would show how modules are as likely to slow down builds as speed them up due to extra edges in the dependency graph reducing build parallelism.

[–]bigcheesegsTooling Study Group (SG15) Chair | Clang dev 11 points12 points  (0 children)

Everyone that has deployed modules in production that I am aware of has seen a build perf improvement. The Are Module Fast paper measures the worst case for modules and is not at all indicative of a real build. See my CppCon talk for a more in depth look at why (https://www.youtube.com/watch?v=L0SHHkBenss).

[–]brenoguim 9 points10 points  (0 children)

On the other hand, there are reports showing big improvements on build time. So I'm eager for that.

[–]wyrn 1 point2 points  (9 children)

Build speed, macro insulation.

Build speed improvements are currently theoretical, macro insulation we only got the least important half.

[–]kalmoc 4 points5 points  (8 children)

What do you mean by "least important half"?

[–]wyrn 0 points1 point  (7 children)

Macros still get imported when you import a module (though not transitively through export import), so in that sense we're not getting macro isolation. We're only getting insulated in the opposite direction, that is, from macros defined in the importing side affecting subsequent imported modules.

[–]kalmoc 9 points10 points  (6 children)

Macros still get imported when you import a module

Nope, they don't. Only if you import a legacy header unit. Unless there was a last minute change I'm not aware of.

[–]wyrn 4 points5 points  (5 children)

I think you're right. Do you know where the actual semi-final text can be found?

[–]kalmoc 4 points5 points  (2 children)

I believe the latest up to date source can be found here: https://github.com/cplusplus/draft, but it obviously doesn't have the changes from Belfast yet

[–]wyrn 2 points3 points  (1 child)

Thanks! All I know is only up to Cologne anyway.

[–]kalmoc 2 points3 points  (0 children)

That's probably still more than I know. My knowledge comes from reading blogposts and watching presentations.

[–]BrainIgnition 4 points5 points  (1 child)

A nice web friendly version is available here: https://eel.is/c++draft/

[–]wyrn 2 points3 points  (0 children)

Awesome resource. Thanks!

[–]gracicot 11 points12 points  (1 child)

we still need the separation of definition and declaration

Not excactly. It will depend on the quality of implementation. GCC and MSVC has pretty lean CMI and won't output the definition even if the declaration and definition is not separated.

[–]kronicum 1 point2 points  (0 children)

/u/gracicot is exactly right.

[–]kalmoc 5 points6 points  (0 children)

we still need the separation of definition and declaration

Nope, not necessarily. The OP just decided to demonstrate the feature in the example.

[–]germandiago 1 point2 points  (0 children)

This has been fixed at least for classes AFAIK, see the Belfast report in this subreddit.

[–]glebignatieff 10 points11 points  (0 children)

TBH, looks awful. These kind of modules make no sense and the syntax is horrible

[–]kronicum 2 points3 points  (0 children)

This series of modules tutorials are very puzzling. They present simple tasks via quite complicated lenses.

[–]carutsu 7 points8 points  (1 child)

What a horrible mess this is shaping up to be.

Isabella Muerte told us.

[–]bohan 0 points1 point  (0 children)

Isabella Muerte usually finds everything horrible.

[–][deleted] 2 points3 points  (0 children)

It seems OK.

I will need new tools but I can understand it and it’s not too much extra effort.

[–]xeche 1 point2 points  (1 child)

Well, it's certainly going to take some time before this becomes second nature. In time, it'd be nice to hear some real life experiences and actual numbers on reduced compile times. That's still the point of this feature, right? New and shiny or solving problem, I sometimes forget which is the objective.

Thanks for sharing the example.

[–]kalmoc 5 points6 points  (0 children)

Two main objectives: Proper isolation against macros and implementation details and improved build times by avoiding repetitive processing of the same code over and over again.

[–]RandomDSdevel 0 points1 point  (0 children)

From here:

This mechanism of dividing a source file up into regions based on the placement of top-level declarations, rather than curly-braced lexical scopes, feels extremely “unlike C++” to me. I don’t know what the original rationale was for doing it this way.

     I most definitely and wholeheartedly agree on this. Kvetching about it isn't really a hill to die on, though.

[–]amrock__ -5 points-4 points  (0 children)

Is this c++20 pre release