Skip to content

Instantly share code, notes, and snippets.

@dgodfrey206
Created February 26, 2026 05:21
Show Gist options
  • Select an option

  • Save dgodfrey206/6869ab6bbc9e0ef2a17fec9dd52f978d to your computer and use it in GitHub Desktop.

Select an option

Save dgodfrey206/6869ab6bbc9e0ef2a17fec9dd52f978d to your computer and use it in GitHub Desktop.
I'm changing `auto x` and `auto y` to `auto a` and `auto b` because `x` shows up in a lot of places and isn't the same name: https://godbolt.org/z/9x5dvcofY
Lines 27 and 28:
auto a = AffineType{};
auto b = AffineType{};
Because AffineType is templated on `InstanceIdentifier` but a template argument isn't provided, the compiler must deduce what it is templated on. There is a default value, which is a lambda. Each lambda is a *unique value*, so the type of `a` and `b` is different - `a = b;` would also be a compiler error. I will be calling those lambda values `LV1` and `LV2`, and so those lines are actually:
27: AffineType<LV1> a{};
28: AffineType<LV2> b{};
29:
30: Use(a);
On line 30, `Use(a)` is once again a template, the compiler replaces it with:
30: Use<LV1, x = ExtractThenUpdateCurrentState<LV1,0,LV3>>(a) requires (x == 0);
Here, `ExtractThenUpdateCurrentState<LV1,0,LV3>` contains a function body whose content depends on
if constexpr (requires { ADLFunc(ADLType<InstanceIdentifier, x>{}); })
return ExtractThenUpdateCurrentState<InstanceIdentifier, x + 1>();
else
InjectDefinitionForADLFunc<InstanceIdentifier, x>{};
return x;
Now the trick makes an appearance.
> About `requires` clauses:
>
> https://godbolt.org/z/an9M4vKEY
>
> auto f(){};
> auto g();
>
> static_assert( requires {f();} ); //compiles
> static_assert( requires {g();} ); //does not compile
>
> Here, `f()` has a body, so its `auto` return type can be deduced (it's `void`), so we can tell that `f()` is a valid expression. Function `g` *might* compile in a different context where we have the body, but we don't *know* that, so the `requires` clause fails.
if constexpr (requires {
ADLFunc(ADLType<InstanceIdentifier, X>{});
} return ...
To go into this branch, there must be a function `ADLFunc` that
- Is findable (argument-dependent lookup finds `ADLType::ADLFunc;`)
- Compiles with these parameters (nope, return value depends on body and there's no body.)
Through argument-dependent lookup, the compiler finds `ADLType::ADLFunc` and *only* that template. It does not satisfy the `requires`-clause, and we take the other branch which does (something haha don't worry about it) and then returns x, which is zero.
Having evaluated all that, we change
30: Use<LV1, x = ExtractThenUpdateCurrentState<LV1,0,LV3>>(a) requires (x==0);
into
30: Use<LV1, x=0>(a) requires(x==0);
Compiles just fine.
Let's go to
33: Use(a);
...Did I say "don't worry about it" earlier? My bad, it's time to worry about it.
16: InjectDefinitionForADLFunc<InstanceIdentifier,x>{};
For the first `Use(a)`, this line was also evaluated. The compiler instantiated template to default-construct an object of that type, then discarded the object. But it *was* instantiated. We now have this type available:
template<LV1,0>
struct InjectDefinitionForADLFunc {
friend consteval auto ADLFunc(ADLType<LV1, 0>, auto...) {}
};
So we call `Use(a)`. Still a template. The compiler still has to find an appropriate match.
Again, we haven't specified anything. Again, the compiler looks for best candidates. Again, it finds:
template<auto InstanceIdentifier, auto x = ExtractThenUpdateCurrentState<InstanceIdentifier>()>
auto Use(AffineType<InstanceIdentifier>) requires (x == 0) {}
It deduces what can be deduced:
Use(a) requires (ExtractThenUpdateCurrentState<LV1> == 0);
And starts looking for `ExtractT...` to see what it evaluates to.
Well, that's still a template:
template<auto InstanceIdentifier, auto x = 0, auto = []{}>
consteval auto ExtractThenUpdateCurrentState()->decltype(x) {
if constexpr (requires { ADLFunc(ADLType<InstanceIdentifier, x>{}); })
Deduce what can be deduced:
template<LV1, auto x = 0, auto = []{}>
consteval auto ExtractThenUpdateCurrentState()->decltype(x) {
Insert defaults:
template<LV1, 0, LV4>
consteval auto ExtractThenUpdateCurrentState()->decltype(0) {
Notice, importantly: `LV4`.
- We didn't specify the lambda argument
- and the default value was not "that lambda from before" it is `[]{}`
- a *new* lambda value
So we're not re-using the function body from before, that was the function body for `LV3`. Time to go over the logic again:
template<LV1, 0, LV4>
consteval auto ExtractThenUpdateCurrentState()->decltype(0) {
if constexpr (requires { ADLFunc(ADLType<LV1, 0>{}); })
To go into this branch, there must be a function `ADLFunc` that
- Is findable (argument-dependent lookup finds `InjectDefinitionForADLFunc<LV1,0>::ADLFunc;`)
- Compiles with these parameters (yup, that's a body, this function returns void)
So we take the *first* branch, which recursively does `x+1` until `x+1` is `INT_MAX+1`. That's UB in all contexts but, importantly, a build break in a constexpr context.
And so, `Use(a)`, on the second call, is a function that can't compile.
Why does `Use(b)` work after `Use(a)`, shouldn't it also find the non-compiling branch? No, because the non-compiling branch depends on `LV1`, which only lives on `a`.
So, @geekfolk, did I miss a step?
In particular, I'm curious why this wouldn't work:
template<auto InstanceIdentifier, auto = []{}>
consteval auto ExtractThenUpdateCurrentState()-> bool {
if constexpr (requires { ADLFunc(ADLType<InstanceIdentifier>{}); })
return false;
else
InjectDefinitionForADLFunc<InstanceIdentifier>{};
return true;
}
It works just fine with the limited example (allows instantiating multiple objects, allows first but not second use of each object)
https://godbolt.org/z/4h1ovPPq6
Is it just a left-over from an earlier attempt, or is it load-bearing for some component of the template machinery that I haven't noticed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment