llvm / llvm/llvm-project

[clang][bytecode] Implicit definition of a defaulted constructor is not available to the bytecode interpreter when the class comes from a module

Open
#221,400 0 comments 0 reactions 0 assignees View on GitHub
clang:bytecode
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

Clang 22.1.8 (Debian, `22.1.8~++20260714014842+ca7933e47d3a`), target `x86_64-pc-linux-gnu`.

With `-fexperimental-new-constant-interpreter`, a defaulted constructor of a class imported
from a C++20 module is reported as undefined when it has to run during constant evaluation.
The default evaluator accepts the same code.

### Reproducer, without the standard library

`m.cppm`:

```cpp
export module m;

export struct allocator_like {
constexpr allocator_like() noexcept = default;
};

export struct box {
allocator_like allocation;
int value = 42;
constexpr box() = default;
[[nodiscard]] constexpr int get() const { return value; }
};
```

`use.cpp`:

```cpp
import m;
consteval int evaluate() {
box b;
return b.get();
}
static_assert(evaluate() == 42);
int main() {}
```

```sh
clang++ -std=c++23 --precompile m.cppm -o m.pcm
clang++ -std=c++23 -fmodule-file=m=m.pcm -fsyntax-only use.cpp
clang++ -std=c++23 -fmodule-file=m=m.pcm -fsyntax-only -fexperimental-new-constant-interpreter use.cpp
```

The first compilation is accepted. The second reports:

```
use.cpp:6:15: error: static assertion expression is not an integral constant expression
m.cppm:10:13: note: undefined constructor 'allocator_like' cannot be used in a constant expression
```

### What it means in practice

The same thing stops any translation unit that uses `import std;` and constructs a
`std::vector` during constant evaluation:

```cpp
import std;
consteval std::size_t evaluate() {
std::vector order;
order.push_back(1);
order.push_back(2);
return order.size();
}
static_assert(evaluate() == 2);
int main() {}
```

```
__vector/vector.h:134:55: note: undefined constructor 'allocator' cannot be used in a constant expression
__memory/allocator.h:80: note: declared here
```

It is that one specialisation. With libc++'s `std` module, element types `char`, `int`,
`long`, `unsigned`, `unsigned long long` and `std::uint32_t` all evaluate; `std::size_t`
(`unsigned long`, which libc++ instantiates inside its own module) does not.

### What narrows it down

- A class from a module whose defaulted constructor has to call the defaulted constructor of
a member of class type: **fails**.
- The same class with the constructor written out (`constexpr allocator_like() noexcept {}`):
accepted.
- A class from a module with a defaulted constructor and no member of class type: accepted.
- Making the module itself use the constructor -- `namespace { constexpr box produced_here{}; }`
inside `m.cppm` -- makes the failing case pass. That suggests the implicit definition is
serialised into the BMI only when the module odr-uses it, and that the importer cannot
produce one for this interpreter. The default evaluator produces it either way.
- Adding a use in the importer instead -- a namespace-scope variable, an ordinary function, or
a `constexpr` variable of the type -- does not help.

Contributor guide

Open the contributing guide

Research direction

Reproduce the failure with m.cppm and use.cpp using the three clang++ commands, then compare the default evaluator with -fexperimental-new-constant-interpreter. Trace how the defaulted constructor and its class-type member are serialized and made available across the module BMI. Done means the importer accepts the shown static_assert and a regression test covers the module case.

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
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.