[C++20 Modules] False "operator() not present" ODR error for a variable-template lambda used across two modules
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
I came across this while working on a reproducer for a different issue.
Two independent modules each pull the same variable template (whose initializer is a lambda) into their global module fragment and instantiate `var`. A third TU that imports both is rejected, even though I'd expect it to compile. We don't even *reach* the `static_assert`, because there's an ODR problem (?) on `operator()`.
```cpp
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
// RUN: cd %t
//
// RUN: %clang_cc1 -std=c++20 -I. m_a.cppm -emit-reduced-module-interface \
// RUN: -o m_a.pcm
//
// RUN: %clang_cc1 -std=c++20 -I. m_b.cppm \
// RUN: -emit-reduced-module-interface \
// RUN: -o m_b.pcm
//
// RUN: %clang_cc1 -std=c++20 -I. main.cpp \
// RUN: -fmodule-file=m_a=m_a.pcm \
// RUN: -fmodule-file=m_b=m_b.pcm \
// RUN: -fsyntax-only -verify
//--- template.h
#pragma once
template
inline auto var = [](T x){ return x; };
//--- m_a.cppm
module;
#include "template.h"
export module m_a;
export auto Trigger1() {
return var;
}
//--- m_b.cppm
module;
#include "template.h"
export module m_b;
export auto Trigger2() {
return var;
}
//--- main.cpp
// expected-no-diagnostics
import m_a;
import m_b;
void use() {
static_assert(__is_same(decltype(Trigger1()), decltype(Trigger2())));
}
```
The test emits errors on trunk: `error: '(lambda)::operator()' from module 'm_b' is not present in definition of '(lambda at template.h:3:19)' in module 'm_a' note: declaration of 'operator()' does not match`.
What seems to be happening: the two closures of var are merged across the modules, but their `operator()` members aren't. The instantiated `operator()` is marked module-local to the instantiating module, and `ASTContext::isSameEntity` then refuses to merge it, so it's reported as missing from the canonical definition. I can post a more extensive walkthrough if needed.
One possible fix: allow merging class members across modules even when marked module-local, as long as their enclosing class/specialization was itself merged: https://github.com/ipopov/llvm-project/commit/cde9ec6dfb7b7f7290a349541e00b1640dab4e36. The tests pass, but it needs review from someone more familiar with the merging logic to confirm it makes sense. I also experimented with avoiding marking these module-local in the first place, but couldn't make it work reliably.
Contributor guide
Research direction
Run the embedded clang_cc1 split-file reproducer using template.h, m_a.cppm, m_b.cppm, and main.cpp, confirming the unexpected operator() diagnostic. Read ASTContext::isSameEntity and the module-local entity-merging logic, then compare the behavior with the referenced proposed change. Done means the reproducer compiles with expected-no-diagnostics while preserving correct module entity handling.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100