`if constexpr (requires{...})` selects the wrong branch inside a generic lambda invoked via `.template operator()<...>()`
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
`if constexpr` picks the `else` branch of a `requires`-expression condition that is actually true, when that condition is checked inside a generic lambda which is itself invoked with an explicit template argument (`.template operator()()`) from inside a function template. GCC accepts the code below; Clang rejects it with a diagnostic on the (genuinely ill-formed, and never supposed to be reached) `else` branch.
### Minimal reproducer
Two functions with the exact same `if constexpr (requires{...}) ... else ...` logic on the same callable — the only difference is whether the check happens directly in the function template's body (`compiles`) or one level deeper, inside a generic lambda invoked via `.template operator()<0>()` (`clang_crashes`):
```cpp
template
void compiles(Func&& func)
{
constexpr int i = 0;
if constexpr (requires { func.template operator()(); })
func.template operator()();
else
func(); // would be ill-formed without explicit <>
}
template
void clang_crashes(Func&& func)
{
[&]()
{
if constexpr (requires { func.template operator()(); })
func.template operator()();
else
func(); // ill-formed without explicit <>
}.template operator()<0>();
}
int main()
{
// invokes the lambda with a == 0 after testing if it has a template parameter
compiles([]() {});
clang_crashes([]() {});
}
```
### Actual behavior (clang 18.1.3 and clang 20.1.2, Ubuntu, `clang++ -std=c++20 -fsyntax-only repro.cpp`)
`compiles` compiles fine. `clang_crashes` does not, identically on both versions:
```
repro.cpp:20:13: error: no matching function for call to object of type '(lambda at repro.cpp:28:19)'
func(); // ill-formed without explicit <>
^~~~
repro.cpp:16:5: note: while substituting into a lambda expression here
repro.cpp:28:5: note: in instantiation of function template specialization 'clang_crashes<(lambda at repro.cpp:28:19)>' requested here
clang_crashes([]() {});
^
repro.cpp:28:19: note: candidate template ignored: couldn't infer template argument 'a'
```
### Expected behavior
Both functions should compile cleanly, as they do with GCC 13.3.0 (`-std=c++20`).
### Related issues
- [#123306](https://github.com/llvm/llvm-project/issues/123306) (open) — closest match, possibly the same root cause: delayed template instantiation across a templated generic lambda boundary not working correctly (cites `[temp.point]p1`), GCC/MSVC accept, Clang rejects. Different surface syntax though: their trigger is a `requires`-clause-constrained member function called from a templated lambda capturing a class instance, not an `if constexpr` branching on a `requires` *expression*. This reproducer is more minimal and isolates the `if constexpr` branch-selection failure specifically, but the two may collapse to the same underlying bug.
- [#73418](https://github.com/llvm/llvm-project/issues/73418) (open) — also a `requires`-expression inside a nested generic lambda, but the symptom is an internal compiler error (via pack expansion in the `requires`-clause), not a wrong branch silently selected.
- [#61387](https://github.com/llvm/llvm-project/issues/61387) (open) — lambda with explicit template parameters and a `requires`-clause using a dependent variable template; different trigger shape.
- [#198987](https://github.com/llvm/llvm-project/issues/198987) / [#198984](https://github.com/llvm/llvm-project/issues/198984) (closed) — same symptom category ("`if constexpr` instantiates the discarded branch") but only reproduces with C++20 modules, unrelated to generic lambdas.
### Versions tested
- Clang: `Ubuntu clang version 18.1.3 (1ubuntu1)`, target `x86_64-pc-linux-gnu` — `clang_crashes` rejected
- Clang: `Ubuntu clang version 20.1.2 (0ubuntu1~24.04.3)`, target `x86_64-pc-linux-gnu` — `clang_crashes` rejected, identical diagnostic
- GCC: `g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0`, `-std=c++20` — both accepted
Contributor guide
Research direction
Start by running the supplied repro.cpp with Clang using `-std=c++20 -fsyntax-only`, then compare the result with GCC. Trace the `if constexpr` and `requires` handling across the explicitly invoked generic lambda; done means Clang accepts both functions without instantiating the discarded branch, with a regression test covering this reproducer.
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
- Clearly specified
- Newbie friendliness
- 48/100