Here is some followup from my earlier attempt to use bleeding edge C++ module support.
First experiment: do I need to import all of the std library?
I tried this:
I didn’t know if gcm.cache/std.gcm (already built from my previous experiment) would supply that export, but I get:
g++ -std=c++23 -fmodules -c -o broken.o broken.cc In module imported at broken.cc:1:1: iostream: error: failed to read compiled module: No such file or directory iostream: note: compiled module file is ‘gcm.cache/iostream.gcm’ iostream: note: imports must be built before being imported iostream: fatal error: returning to the gate for a mechanical issue compilation terminated. make: *** [: broken.o] Error 1
so it appears the answer is no. Also, /usr/include/c++/15/bits/ only appears to have a std.cc, and no iostream.cc:
> find /usr/include/c++/15/bits/ -name "*.cc" /usr/include/c++/15/bits/std.cc /usr/include/c++/15/bits/std.compat.cc
so it appears, for the time being, g++-15 is all or nothing with respect to std imports. However, when using precompiled headers, you usually want a big pre-generated pch that has just about everything, and this is similar, so maybe that’s not so bad (other than namespace pollution.)
Second experiment. Adding a non-std import/export.
I moved a variant of Stroustrup’s collect_lines function into a separate module, like so:
// stuff.cc
export module stuff;
import std;
namespace stuff {
void helper() { std::cout << "call to a private function\n"; }
export
std::vector<std::string> collect_lines(std::istream &is) {
helper();
std::unordered_set<std::string> s;
for (std::string line; std::getline(is, line);)
s.insert(line);
//return std::vector<std::string>(s.begin(), s.end());
return std::vector{std::from_range, s};
}
} // namespace stuff
It turns out that I needed the export keyword on ‘module stuff’, as well as for any function that I wanted to export. Without that I get:
> make
g++ -std=c++23 -fmodules -c /usr/include/c++/15/bits/std.cc
g++ -std=c++23 -fmodules -c -o stuff.o stuff.cc
g++ -std=c++23 -fmodules -c -o try.o try.cc
try.cc: In function ‘int main()’:
try.cc:8:12: error: ‘stuff’ has not been declared
8 | auto v = stuff::collect_lines(std::cin);
| ^~~~~
make: *** [: try.o] Error 1
The compile error is not very good. It doesn’t complain that collect_lines is not exported, but instead complains that stuff, the namespace itself, is not declared.
I can export the namespace, which is the naive resolution to the compiler diagnostic presented, for example:
export module stuff;
import std;
export namespace stuff {
void helper() { std::cout << "call to a private function\n"; }
//export
std::vector<std::string> collect_lines(std::istream &is) {
helper();
std::unordered_set<std::string> s;
for (std::string line; std::getline(is, line);)
s.insert(line);
//return std::vector<std::string>(s.begin(), s.end());
return std::vector{std::from_range, s};
}
} // namespace stuff
However, that means that the calling code can now call stuff::helper, which was not my intent.
There also does not appear to be any good way to enumerate exports available in the gcm.cache. nm output for the symbol is not any different with or without the export keyword:
> nm stuff.o | grep collect_lines | c++filt 0000000000000028 T stuff::collect_lines@stuff[abi:cxx11](std::basic_istream<char, std::char_traits >&)
This is a critically important tooling failure if modules are going to be used in production. Anybody who has programmed with windows dlls or AIX shared objects, or Linux shared objects with symbol versioning, knows about the resulting hellish nature of the linker error chase, when an export is missed from such an enumeration. Hopefully, there’s some external tool that can enumerate gcm.cache exports. Both grok and chatgpt were unsuccessful advising about tools for this sort of task. The best answer was chatgpt’s recommendation for -fmodule-dump:
> g++ -std=c++23 -fmodules -save-temps -fdump-lang-module -c -o stuff.o stuff.cc fedoravm:/home/peeter/physicsplay/programming/module> ls broken.cc gcm.cache makefile makefile.clang std.o stuff.cc stuff.cc.002l.module stuff.ii stuff.o stuff.s try.cc
but that *.module output doesn’t have anything that obviously distinguishes exported vs. non-exported symbols:
> grep -2e stuff::helper -e stuff::collect_lines *.module Wrote section:28 named-by:'::std::vector<::std::__cxx11::basic_string@std:1<char,::std::char_traits@std:1,::std::allocator@std:1>,::std::allocator<::std::__cxx11::basic_string@std:1<char,::std::char_traits@std:1,::std::allocator@std:1>>>' Writing section:29 2 depsets Depset:0 decl entity:403 function_decl:'::stuff::collect_lines' Wrote declaration entity:403 function_decl:'::stuff::collect_lines' Depset:1 binding namespace_decl:'::stuff::collect_lines' Wrote section:29 named-by:'::stuff::collect_lines' Writing section:30 2 depsets Depset:0 decl entity:404 function_decl:'::stuff::helper' Wrote declaration entity:404 function_decl:'::stuff::helper' Depset:1 binding namespace_decl:'::stuff::helper' Wrote section:30 named-by:'::stuff::helper' Writing section:31 4 depsets Depset:0 specialization entity:405 type_decl:'::std::__replace_first_arg<::std::allocator<::std::__detail::_Hash_node<::std::__cxx11::basic_string@std:1<char,::std::char_traits@std:1,::std::allocator@std:1>,0x1>>,::std::__cxx11::basic_string@std:1<char,::std::char_traits@std:1,::std::allocator@std:1>>' -- Writing binding table Bindings '::std::operator==' section:8 Bindings '::stuff::collect_lines' section:29 Bindings '::stuff::helper' section:30 Bindings '::std::swap' section:35 Writing pending-entities
Chatgpt summarizes this as follows:
“This is confirmed by overwhelming evidence:
- GCC bug 113590
- GCC mailing list discussion July 2024
- Confirmation from module implementers: “GCC BMIs do not currently record export flags.”
This is intentional (for now): GCC’s binary module interface tracks reachable declarations, not exported ones.”
Trying clang
After considerable experimentation, and both grok and chatgpt help, I was finally able to get a working compile and link sequence using the clang toolchain:
fedoravm:/home/peeter/physicsplay/programming/module> make -f *.clang clean rm -f *.o *.pcm try fedoravm:/home/peeter/physicsplay/programming/module> make -f *.clang clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -Wno-reserved-module-identifier --precompile /usr/share/libc++/v1/std.cppm -o std.pcm clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -fmodule-file=std=std.pcm --precompile stuff.cppm -o stuff.pcm clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -fmodule-file=std=std.pcm -fmodule-file=stuff=stuff.pcm -c try.cc -o try.o clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -fmodule-file=std=std.pcm -c stuff.cc -o stuff.o clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -fmodule-file=std=std.pcm -fmodule-file=stuff=stuff.pcm try.o stuff.o -o try
Unlike g++, I have to build both the module and the object code for stuff.cc (and facilitated that with a clang.cppm -> clang.cc symlink), but unlike g++, I didn’t need a std.o (for reasons that I don’t understand.)
Dumping the clang-AST appears to be the closest that we can get to enumerating exports. Example:
> clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -fmodule-file=std=std.pcm -Xclang -ast-dump -fsyntax-only stuff.cc | less -R
This shows output like:
This is not terribly user friendly, and not something that a typical clang front end user would attempt to do.
This hints that the “way” do dump exports would be to write a clang-AST visitor that dumps all the ExportDecl’s that are encountered (or a complex grep script that attempts to mine the -ast-dump output)



