Created
February 26, 2026 05:21
-
-
Save dgodfrey206/6869ab6bbc9e0ef2a17fec9dd52f978d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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