https://gcc.gnu.org/bugzilla/show_bug.cgi?id=125230
Bug ID: 125230
Summary: [modules] #include transformed into import
Product: gcc
Version: 16.1.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: rainerd at eldwood dot com
Target Milestone: ---
Created attachment 64394
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=64394&action=edit
example project that demonstrates the problem
It seems that when compiling with C++ modules, #include statements are silently
transformed into import statements. This may be a clever optimization in some
cases, but I believe it is not standard-conforming and it breaks real code.
Consider a header-only library like Doctest. Such libraries often use a
preprocessor macro to conditionally compile function definitions in order to
compile them exactly once. Here is a minimal example:
a.hpp (the library, cannot be changed):
void f();
#ifdef A_IMPL
void f() {
}
#endif
b.cpp (part of the program that uses the library):
#define A_IMPL
#include "a.hpp"
c.cpp (part of the program that uses the library):
import "a.hpp";
int main() {
f();
}
test.sh (the build script):
~/toolchains/gcc/16.1.0/bin/g++ -Wall -Wextra -fmodules -x c++-header a.hpp
~/toolchains/gcc/16.1.0/bin/g++ -Wall -Wextra -fmodules -c b.cpp
~/toolchains/gcc/16.1.0/bin/g++ -Wall -Wextra -fmodules -c c.cpp
~/toolchains/gcc/16.1.0/bin/g++ -Wall -Wextra c.o b.o
Output:
/usr/bin/ld: c.o: in function `main':
c.cpp:(.text+0x5): undefined reference to `f()'
collect2: error: ld returned 1 exit status
Changing the import in c.cpp into a #include does not change the error. The
mere existence of gcm.cache/,/a.hpp.gcm on the file system is enough to turn
the #include into an import, even if the program consistently uses #include
over import. However, the problem does not appear if a.hpp is never compiled.
For reference, here are the preprocessed files, confirming that #include has
been transformed into import:
b.ii:
# 0 "b.cpp"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3
# 0 "<command-line>" 2
# 1 "b.cpp"
c.ii:
# 0 "c.cpp"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3
# 0 "<command-line>" 2
# 1 "c.cpp"
import "./a.hpp";
int main() {
f();
}
import "./a.hpp" [[__translated]];
I have attached the complete example, including preprocessed files.
Information on my build environment:
rainer@rainer10:~/work/gcc_header_unit_test$ ~/toolchains/gcc/16.1.0/bin/gcc
-v
Using built-in specs.
COLLECT_GCC=/home/rainer/toolchains/gcc/16.1.0/bin/gcc
COLLECT_LTO_WRAPPER=/home/rainer/toolchains/gcc/16.1.0/libexec/gcc/x86_64-pc-linux-gnu/16.1.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ../configure --prefix=/home/rainer/toolchains/gcc/16.1.0
--disable-multilib
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 16.1.0 (GCC)
rainer@rainer10:~/work/gcc_header_unit_test$ uname -a
Linux rainer10 6.8.0-111-generic #111-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 11
23:16:02 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux