https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126192

            Bug ID: 126192
           Summary: Duplicate global-module-fragment entities from two
                    imported modules fail to merge, causing spurious
                    ambiguous-conversion errors
           Product: gcc
           Version: 16.1.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: avi at scylladb dot com
  Target Milestone: ---

When two named modules each pull the same header into their global module
fragment (GMF), the GMF entities are duplicated (one copy per module). If a
translation unit imports both modules and one of them re-exports an
enumerator of an unnamed enum, that enumerator is not unified across the two
modules. Anything that then depends on it breaks with

error: reference to 'max_packed_args' is ambiguous

Two conditions are both necessary (and, together, sufficient):

1. The entity is an enumerator of an unnamed enum. A named enum, an
enum class, or a plain inline constexpr int all merge correctly and do
not reproduce — so the failure is specific to merging the anonymous
enum.

2. A module re-exports that enumerator with a using-declaration. Re-exporting
a sibling (a type, or the dependent alias itself) does not trigger it.

Reproducer:

// base.h
#ifndef BASE_H
#define BASE_H
namespace demo {
enum { max_packed_args = 6 };                 // enumerator of an UNNAMED enum
template <bool B, class T, class F> struct cond { using type = F; };
template <class T, class F> struct cond<true, T, F> { using type = T; };
template <class T> struct a_t {};
template <class T> struct b_t {};
template <class Context, int N>
using arg_t = typename cond<(N <= max_packed_args), a_t<Context>,
b_t<Context>>::type;
}
#endif

// fmt.cppm
module;
#include "base.h"
export module fmt;
export namespace demo { using demo::max_packed_args; }   // <-- the trigger

// seastar.cppm
module;
#include "base.h"
export module seastar;
export namespace seastar { inline int dummy = 0; }

// main.cc
import seastar;
import fmt;
#include "base.h"
int main() {}

This was first found on clang [1].

Credit for the characterization and minimization: Claude Opus 4.8 (though I did
have to encourage it when the going was tough).


[1] https://github.com/llvm/llvm-project/issues/207581

Reply via email to