https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124311
Bug ID: 124311
Summary: [modules] Defaulted-in-class special member re-emitted
across modules causes "cannot be overloaded with
itself"
Product: gcc
Version: 16.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: kachalenko.denis at gmail dot com
Target Milestone: ---
When a struct with an explicitly defaulted special member function
(= default; in the class body) is exported from module A, and an
importing module B triggers synthesis of that member, a downstream
consumer that loads both modules gets:
error: 'constexpr S@A::S()' cannot be overloaded with 'constexpr S@A::S()'
This is a regression introduced by commit 5b85364a6dd0 (PR c++/120499).
Reproduces with -std=c++20, -std=c++23, and -std=c++26.
Reproducer (11 lines, 3 files)
==============================
// a.cppm
export module A;
export struct S {
int x = 0;
S() = default;
};
// b.cppm
export module B;
export import A;
export struct M { S h; };
M m;
// t.cpp
import B;
M m2;
$ g++ -std=c++26 -fmodules -c a.cppm # OK
$ g++ -std=c++26 -fmodules -c b.cppm # OK
$ g++ -std=c++26 -fmodules -c t.cpp # ERROR
Error output
============
In module A, imported at b.cppm:2,
of module B, imported at t.cpp:1:
a.cppm:4:9: error: 'constexpr S@A::S()' cannot be overloaded with 'constexpr
S@A::S()'
4 | S() = default;
| ^
a.cppm:4:9: note: previous declaration 'constexpr S@A::S()'
a.cppm:4:9: error: 'constexpr S@A::S()' cannot be overloaded with 'constexpr
S@A::S()'
a.cppm:4:9: note: previous declaration 'constexpr S@A::S()'
t.cpp:2:1: note: during load of binding '::M@B'
2 | M m2;
| ^
Version
=======
gcc version 16.0.1 20260221 (experimental) (GCC)
Target: x86_64-w64-mingw32
Essentiality
============
Every element is required; removing any one makes it compile:
- Remove `S() = default;` --> OK (implicit ctor, lazy flag stays true)
- Remove `int x = 0;` --> OK (no NSDMI, ctor not constexpr)
- Remove `M m;` --> OK (no variable forces synthesis in B)
- Remove wrapper struct M --> OK (S s; alone doesn't trigger)
- Move = default outside class --> OK (not defaulted-in-class)
`export import` is not essential; plain `import A` still triggers the bug
as long as B re-emits S's ctor and the consumer loads the relevant section.
Regression
==========
Regressed with commit 5b85364a6dd0bbfd3e26d3346b075a0819be7cd4:
c++/modules: Provide definitions of synthesized methods outside their
defining module [PR120499]
* method.cc (synthesize_method): Set the instantiating module.
That commit added two lines to synthesize_method in method.cc:
finish_function_body (stmt);
finish_function (/*inline_p=*/false);
+ /* Remember that we were defined in this module. */
+ set_instantiating_module (fndecl);
if (!DECL_DELETED_FN (fndecl))
expand_or_defer_fn (fndecl);
Root cause analysis
===================
Writer side (module B)
----------------------
1. Module B imports A. S::S() is loaded as entity @A:1 with
DECL_MODULE_IMPORT_P = true. Because S() = default; is an explicit
declaration, CLASSTYPE_LAZY_DEFAULT_CTOR(S) = false.
2. `M m;` forces synthesis of M::M(), which in turn triggers
synthesize_method(S::S()).
3. set_instantiating_module(fndecl) unconditionally clears
DECL_MODULE_IMPORT_P (module.cc:21969).
4. set_defining_module(fndecl) sees the class context is imported
(module.cc:21991) and adds the ctor to class_members.
5. add_class_entities creates a non-imported depset via make_dependency
(because DECL_MODULE_IMPORT_P is now false).
6. B.gcm gets a separate section:1 for S@A:1::__ct with its own
definition -- a duplicate of what's already in A.gcm's class
definition of S.
-fdump-lang-module evidence -- A.gcm writes S::__ct as a declaration
only (body not separately streamed because DECL_SAVED_TREE is null
for uninstantiated defaulted members):
Depset:1 decl entity:1 function_decl:'::S::__ct '
Writing class definition '::S'
B.gcm re-emits S::__ct with a full definition:
Writing section:1 1 depsets
Depset:0 decl entity:0 function_decl:'::S@A:1::__ct '
Writing definition '::S@A:1::__ct '
Reader side (consumer t.cpp)
----------------------------
7. t.cpp imports B (which re-exports A). The reader first loads A.gcm:
S's class definition is read, CLASSTYPE_LAZY_DEFAULT_CTOR(S) becomes
false, and S::S() is installed as a class member.
8. Then B.gcm section:1 is read. The reader encounters S::S() as a
new entity and calls install_implicit_member (module.cc:9024).
9. install_implicit_member checks CLASSTYPE_LAZY_DEFAULT_CTOR(S) --
it's already false (ctor was installed from A.gcm). Returns false.
set_overrun() triggers the "cannot be overloaded with itself" error.
Why PR120499's fix is correct for its case but not ours
-------------------------------------------------------
PR120499 fixed a linker error where _Vector_impl<int>::~_Vector_impl()
was never emitted. In that case:
- The destructor was implicitly declared (not = default; in class body)
- Module A (std) never synthesized it --> no definition in A.gcm
- CLASSTYPE_LAZY_DESTRUCTOR = true in A.gcm
- Module B synthesizes it --> set_instantiating_module clears import flag
--> B.gcm streams the definition --> C can use it
- Reader: install_implicit_member finds CLASSTYPE_LAZY_DESTRUCTOR = true
--> installs successfully
In our case:
- The ctor is explicitly defaulted (S() = default;) in the class body
- A.gcm has S's class definition which includes the ctor declaration
- CLASSTYPE_LAZY_DEFAULT_CTOR = false in A.gcm
- B re-emits the ctor --> C gets two definitions --> reader fails
The fix clears DECL_MODULE_IMPORT_P unconditionally, but it should only
do so when the member's definition is not already available from the
home module's class definition.
Suggested fix
=============
Guard set_instantiating_module in synthesize_method to skip members that
are explicitly defaulted in the class body of an imported class. Such
members have CLASSTYPE_LAZY_* already false in the home module's CMI,
so re-emitting them causes the reader-side deduplication failure.
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -1853,8 +1853,15 @@ synthesize_method (tree fndecl)
finish_function_body (stmt);
finish_function (/*inline_p=*/false);
- /* Remember that we were defined in this module. */
- set_instantiating_module (fndecl);
+ /* Remember that we were defined in this module. But skip this for
+ members that were explicitly defaulted in the class body of an
+ imported class -- their definition is already covered by the home
+ module's class definition section, and clearing the import flag
+ would cause the writer to re-emit them, leading to a deduplication
+ failure when a downstream importer reads both definitions. */
+ tree fn = STRIP_TEMPLATE (fndecl);
+ if (!(DECL_LANG_SPECIFIC (fn) && DECL_MODULE_IMPORT_P (fn)
+ && DECL_DEFAULTED_IN_CLASS_P (fn)))
+ set_instantiating_module (fndecl);
if (!DECL_DELETED_FN (fndecl))
expand_or_defer_fn (fndecl);
This preserves PR120499's fix for lazily-declared implicit members
(DECL_DEFAULTED_IN_CLASS_P is false for those), while preventing
re-emission of explicitly-defaulted-in-class members whose definitions
are already implied by the home module's class definition.