Introduction
In the 6th episode, we will include a header for types we will use in our exported interface.
For that purpose we will extend our interface with 2 methods:
std::vector<std::string> doubleString(const std::string& input);std::string reverse(const std::string& input);
2 plain stupid functions, in order to make a point.
Main will use it as follows:

The c_str() and puts() will become clear later on, normally I would not use those. Just for the sake of an experiment.
Old Style
Let’s first reflect on how we would do that in the old traditional way, where our interface is exported via a header file.
Their implementation is as follows (we will put it in Numbers1.cpp):

The more interesting discussion is, how the header should be adjusted, sure we add the declarations and we will do more.
We know that headers should be self sustained, sometimes also expressed as:
- they should be correct
- they should be complete
- they should compile on their own
- …
Well indeed, if we would create a source file “whatever.cpp” which only has one statement: #include "Numbers.h" . This file should compile.
So this means a header must ensure it includes the things it needs for this purpose (and for other valid purposes).
In our case, this boils down to:

Alright, a good practice we have mastered many decades ago.
The main question, is how will this translate into modules. Let’s inspect.
Modules way
First let’s get the implementation out of the way (with one open question), as with the old style case, we just add the implementation to Numbers1.cpp.
The open question, should we include the 2 headers in the global module fragment of Numbers1.cpp ? The answer will become obvious shortly.
The primary module interface unit
From last episode we have learned 2 things:
- headers should be included in the global module fragment
- nothing from this section gets exported
Are you worried ? This says the includes will not propagate, and remain a local/boxed/sealed thing. So how will the users get their includes, are they forced, to peak into the global module fragment, and copy/paste that list of includes ?
Oh boy, and even worse, ensure it remains in sync with updates of the interface of the module. Really, are we creating problems … ?
This is how our primary module interface unit looks like now:

Before diving into the details, let’s carry out some experiments … and be surprised (or not).
First problem, our Number1.cpp does not build
/home/lieven/Teaching/Modules/AppContainingModule/IncludingHeaderInInterface/Module/src/Numbers1.cpp:14:6: error: ‘vector’ in namespace ‘std’ does not name a template type
14 | std::vector<std::string> doubleString(const std::string& input)
and more, basically also stating std::string is unknown.So we need to add the includes here too … Let’s just do it.
And now it builds.
What, what, it builds you say ? But we didn’t include the headers in main.cpp, which is using the exported methods. How does main now about vector and string ?
And this, dear reader, is where the mindfuck happens … visibility versus reachability …
Visible
When something is exported from a module (remember we can export, a function, a type, a variable, a template) the name of that thing is visible to the user. So when you can see the name, well you can spell it out.
So in main we can spell out, our 4 functions.
Not Visible
But nobody exported vector, nobody exported string, so these we can’t spell out ourselves. So how can we use for example the return type of our “doubleString()” function ?
Well the answer was obviously already in our code, but let’s for example turn to a more or less similar use case, where we can’t spell out the name of a type:

Indeed we can not mention the type of a lambda stored in a variable. Solution: “auto”. And we can declare our variable, and later on call it.
So we say, it is not visible, but it is reachable, our lambda was reachable and we could even interact with it (aka call it).
And this reachability is actually a “chain of”, we can reach the vector, and as such get hold of a element (as long as we don’t spell it out as a string, so use auto again), we can reach the string. And so on, and so on.
See the following test snippet:

Which will give the output: 242.
So why are we using puts, and not just println or cout ? Because the headers of those seem to bring vector and string (<print> gave both, <iostream> only string). As such we need to include in main.cpp: #include <cstdio>
Let’s for fun, adjust the subpart line into: spelling out what we can’t spell out.

The compiler complains:
/home/lieven/Teaching/Modules/AppContainingModule/IncludingHeaderInInterface/Module/src/main.cpp:20:10: error: ‘string’ is not a member of ‘std’
20 | std::string subpart = first.substr(1);
| ^~~~~~Which is in line with the fact it is not exported, and as such not visible, but reachable.
This is also the reason we needed the 2 includes in our implementation file, because in there we were explicitly naming both the vector and string types.
Notice how the reachability is really a chain: vector<string> –> string –> c_str(), or string –> substr() -> c_str().
Recap:
- Symbols that are reachable but not visible can occur when an exported API provides access to a type that is not exported. This type can be used indirectly.
- When importing a module, you also indirectly import all types that are used by the exported API, you can use those types including all of their member functions
- The behavior that indirectly exported symbols are not visible but reachable allows programmers to use different modules that use the same symbol names in their exported interface
The big question is, is this a good thing ? I am not sure yet, I will come back with the same question once we revisit this code example in the context of the std module, and we will be doing “using std;”, because we have another trick up our sleeves when things get delivered via modules only and not via headers.
So it feels bad, because we kind of enforce the user, to find out the includes from the global segment of the primary module interface, the user needs to ‘repeat’. Typically this will only be a subset, not always every user needs to call all the things exported, or as we have seen “auto” can save a lot of the burden.
But the moment such a type is actually needed, let’ say for the following code snippet, we need the include:

On the other hand one could argue:
- auto saves the day, so why bother
- and when you explicitly use a certain type (like std::string for the variable name), one could argue, you need it, since the variable is in the user code. But then again, maybe that variable and use of that specific type is just there to be able to call the exported function.
Good thing, bad thing ?
Probably this will be confusing to many developers, it is less DRY, at this moment I tend to think: this is not good UX.
Summary
We saw how to include a header in the primary module interface. This is rather trivial, but if that include brings types, which are part of the exported API, and those themselves are not exported, the distinction between visibility and reachability comes into play.
And in several cases this will enforce the user to ‘repeat’ some of those includes in the end. This feels annoying, and seems to create a maintenance burden:
- next version of the module might require to add a few more includes
- next version of the module might require a thorough inspection to see of some of those repeated includes are no longer needed
Do note, this maintenance burden can only pop up when something changes to the exported interface:
- a new method added, needing a new type => we are not using that method (yet), so no maintenance
- an existing method we use, got a new/changed parameter, needing a new type => maintenance needed anyway since ,we need to adjust the using code for the API change
This seems to indicate it might not be such a problem after all, but it remains a fact it is less DRY compared to the past.
We will come back on this topic, once we are using the std module.
Next Episode
In a next episode we will investigate a bit, the name mangling in the view of modules, and what “attached to the module” means. This is something that makes including a header in the module purview a bad idea.

Leave a Reply