Understanding the GCC 15 Compilation Error with std::vector and Modules

When adopting C++20 and C++23 modules with GCC 15, you may encounter a puzzling compilation error when using standard library containers like std::vector inside an exported template. Typically, calling methods such as push_back() or emplace_back() triggers an error similar to this:

/opt/gcc-15/include/c++/15.2.0/type_traits:1672:18: error: no matching function for call to ‘operator new(sizetype, void*)’
 1672 |         noexcept(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...))
      |                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Oddly enough, adding #include <memory_resource> or #include <new> inside main.cpp makes the problem disappear. Let’s dive into why this happens and how to resolve it properly.

The Root Cause: Missing Placement operator new Declaration

The core of this issue lies in the intersection of template instantiation, C++ Modules name lookup, and the implementation of std::allocator_traits in GCC's libstdc++.

  • Template Instantiation in the Importer: The class template ResourceMan<T> is defined inside module ResourceManager, but it is instantiated in main.cpp when you use ResourceMan<int> man; man.UploadResource(1);.
  • Dependent Name Lookup for Placement New: During instantiation, std::vector::push_back invokes std::allocator_traits<std::allocator<int>>::construct, which tests whether constructing an int via placement new is noexcept. It uses the global placement new expression: ::new(static_cast<void*>(ptr)) int(res).
  • Module Isolation: In standard C++ modules, headers included in the Global Module Fragment (between module; and export module ...;) do not leak their macro definitions, internal declarations, or global symbols into translation units that import the module. While <vector> indirectly includes internal declarations of operator new(size_t), standard placement new (void* operator new(std::size_t, void*) noexcept) is explicitly declared in <new>.

Because main.cpp does not include <new>, the declaration for operator new(size_t, void*) is not visible in main.cpp when the compiler attempts to instantiate the template. Thus, overload resolution fails, and GCC complains that no suitable operator new exists.

How to Fix the Issue

1. Modern Approach: Use Standard Library Modules (import std;)

If you are already on C++23 with GCC 15 and CMake 3.28+, the cleanest and standard-conformant way forward is to rely on standard library modules instead of including headers in the Global Module Fragment.

Update your ResourceManager.ixx:

export module ResourceManager; // No Global Module Fragment needed

import std;

export template <typename T_Res>
class ResourceMan
{
private:
    std::vector<T_Res> _buffer;

public:
    void UploadResource(const T_Res& res)
    {
        _buffer.push_back(res);
    }
};

In your main.cpp:

import std;
import ResourceManager;

int main()
{
    ResourceMan<int> man;
    man.UploadResource(1);
    return 0;
}

Ensure your CMake configuration supports standard module scanning by enabling CMAKE_EXPERIMENTAL_CXX_IMPORT_STD if required by your specific compiler build.

2. Explicitly Export or Include <new>

If you prefer or need to stick with header includes in the Global Module Fragment, you must make the placement new declaration available to translation units instantiating your templates.

Option A: Include <new> in main.cpp

Including <new> provides the global definition of placement new:

#include <new> // Brings operator new(std::size_t, void*) into scope

import ResourceManager;

int main()
{
    ResourceMan<int> man;
    man.UploadResource(1);
    return 0;
}

Option B: Export the Placement New Header from the Module Interface

You can export <new> directly from the module interface so that any translation unit importing ResourceManager automatically receives the required declarations:

module;

#include <vector>

export module ResourceManager;

// Export standard placement new for consumers of this template
export import <new>;

export template <typename T_Res>
class ResourceMan
{
    // ...
};

Summary

You are not doing anything fundamentally wrong—this is an edge-case artifact of C++20 module symbol boundaries combined with template instantiation across translation units. In GCC 15, placement new must be visible at the point of instantiation. Transitioning to import std; or including <new> directly resolves the lookup failure.