Skip to content

[modules] Template specialization instantiated twice across module boundary, causing duplicate initialization of inline static data member #155969

@Eromythic

Description

@Eromythic

Summary
When a class template with an inline static data member is defined inside an anonymous namespace within a module interface unit, clang instantiates the specialization twice. As a result, the inline static data member is initialized twice at runtime, leading to duplicated side effects. MSVC does not exhibit this behavior.

Reproducer
main.cpp:

import demo;

int main() { }

demo.ixx:

module;
#include <iostream>
#include <string>
#include <typeindex>
#include <unordered_map>
export module demo;

class Registry {
public:
    static Registry& instance() {
        static Registry instance;
        return instance;
    }

    template<class Derived> void register_type() {
        const std::string type_name = typeid(Derived).name();
        if (auto it = m_type_to_typeindex.find(type_name); it != m_type_to_typeindex.end()) {
            std::cout << "Trying to re-register class: " << typeid(Derived).name()
                << " (prev: " << it->second
                << ", new: " << std::type_index(typeid(Derived)).hash_code()
                << ")" << std::endl;
            return;
        }
        m_type_to_typeindex[type_name] = std::type_index(typeid(Derived)).hash_code();
    }

private:
    std::unordered_map<std::string, size_t> m_type_to_typeindex;
};

template<class Derived>
class Registrable {
protected:
    Registrable() { (void)registered; }

private:
    static void _register() {
        Registry::instance().template register_type<Derived>();
    }
    inline static bool registered = (_register(), true);
};

namespace {
class MyClass : public Registrable<MyClass> {
public:
    MyClass() : Registrable() {}
};
}

Steps to reproduce:
clang-cl:

  1. clang++ -std=c++23 -x c++-module demo.ixx --precompile -o demo.pcm
  2. clang++ -std=c++23 -x c++-module -c demo.ixx -fmodule-file=demo=demo.pcm -o demo.o
  3. clang++ -std=c++23 -c Main.cpp -fmodule-file=demo=demo.pcm -o Main.o
  4. clang++ Main.o demo.o -o demo_app

clang:

  1. clang++ -std=c++23 -x c++-module demo.ixx --precompile -o demo.pcm
  2. clang++ -std=c++23 -x c++-module -c demo.ixx -fmodule-file=demo=demo.pcm -o demo.o
  3. clang++ -std=c++23 -c Main.cpp -fmodule-file=demo=demo.pcm -o Main.o
  4. clang++ Main.o demo.o -o demo_app
  5. Run demo_app and observe "Trying to re-register class..." is printed.

Observed behavior (clang-cl):

  • Registrable<MyClass> is instantiated twice.
  • Each instantiation has its own inline static bool registered.
  • Both registered variables are initialized at startup, so _register() runs twice, producing duplicate registration attempts in Registry.

Expected behavior:
Its inline static member registered should be initialized once, and _register() should run only once.

Notes:

  • Moving MyClass out of the anonymous namespace prevents the problem.
  • Replacing the "module import" with a "header include" also prevents the problem.

Version:

clang version 20.1.8 and 21.1.0
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files\LLVM\bin

Metadata

Metadata

Assignees

No one assigned

    Labels

    clang:modulesC++20 modules and Clang Header Modules

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions