`requires` expression result changes depending on prior template instantiation order
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
# `requires` expression result changes depending on prior template instantiation order
## Description
The result of a `requires` expression checking aggregate initialization validity changes depending on which types have been previously checked. Evaluating `can_init` followed by `can_init` causes the second to return `false`, even though both should return `true`. Swapping the evaluation order causes the first to pass and the second to fail. Either type checked in isolation passes.
## Minimal Reproducer
```cpp
#include
#include
#include
#include
struct any_t {
template
constexpr operator T() const;
};
template
constexpr bool can_init = requires { T{any_t{}}; };
struct B;
using V = std::variant;
struct W { std::unique_ptr p; };
struct A { int x; std::tuple y; };
struct B { int x; std::tuple y; };
static_assert(can_init); // OK
static_assert(can_init); // FAILS: evaluates to false
```
Both `A` and `B` are 2-member aggregates. `T{any_t{}}` should be a valid aggregate initialization for both: the first member is initialized from `any_t{}` and the second member (a `std::tuple`) is value-initialized. Each `static_assert` passes when the other is removed.
Swapping the two `static_assert` lines causes `can_init` to fail instead:
```cpp
static_assert(can_init); // OK (now evaluated first)
static_assert(can_init); // FAILS (now evaluated second)
```
## Expected Behavior
Both `static_assert`s pass regardless of order.
## Actual Behavior
Whichever `can_init` is evaluated second always fails.
## Compiler Versions
- **Fails:** Apple Clang 17.0.0 (clang-1700.4.4.1), arm64-apple-darwin25.3.0 (also tested on compiler explore with Clang trunk for x86)
- **Passes:** GCC 14.3.0
## Notes
During evaluation of the first `can_init`, Clang instantiates `any_t::operator W()` as part of overload resolution inside `std::tuple`'s constructor, producing the warning:
```
warning: inline function 'any_t::operator W' is not defined [-Wundefined-inline]
```
This appears to create cached template instantiation state that corrupts the `requires` evaluation for the second type.
All of the following conditions are necessary to trigger the bug:
- `std::unique_ptr` (raw pointer and `std::shared_ptr` do not trigger it)
- `std::variant` containing a forward-declared type that is self-referential (the struct is in its own variant)
- `std::tuple` wrapping (not `std::pair` or direct struct members)
- Two structs with **different** `std::tuple` arities of the same wrapper type
- Both `can_init` evaluations present (either alone passes)
This may be related to [#134148](https://github.com/llvm/llvm-project/issues/134148), which reports an assertion failure in `SemaTemplateInstantiate.cpp` triggered by a similar pattern.
This bug was discovered while investigating [Glaze #2234](https://github.com/stephenberry/glaze/issues/2234), where it causes the library's compile-time member counting to silently return 0 for affected types.
Contributor guide
Research direction
Start by compiling the minimal reproducer with the affected Clang and reversing the two static_assert evaluations to confirm the order-dependent requires result. Trace can_init through aggregate initialization, std::tuple construction, and any_t::operator W; done means both assertions pass in either order without the cached-instantiation failure.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100