Using a hierarchy to show template errors is brilliant and I'm sort of surprised compilers haven't always done that.
I was investigating C++-style templates for a hobby language of mine and SFINAE is an important property to make them work in realistic codebases, but leads to exactly this problem. When a compile error occurs, there isn't a single cause, or even a linear chain of causes, but an potentially arbitrarily large tree of them.
For example, it sees a call to foo() and there is a template foo(). It tries to instantiate that but the body of foo() calls bar(). It tries to resolve that and finds a template bar() which it tries to instantiate, and so on.
The compiler is basically searching the entire tree of possible instantiations/overloads and backtracking when it hits dead ends.
Yes, it's definitely nice to be able to typecheck generic code before instantiation. But supporting that ends up adding a lot of complexity to the typesystem.
C++-style templates are sort of like "compile-time dynamic types" where the type system is much simpler because you can just write templates that try to do stuff and if the instantiation works, it works.
C++ templates are more powerful than generics in most other languages, while not having to deal with covariance/contravariance, bounded quantification, F-bounded quantification, traits, and all sorts of other complex machinery that Java, C#, etc. have.
I still generally prefer languages that do the type-checking before instantiation, but I think C++ picks a really interesting point in the design space.
Please no. Templates being lazy is so much better than Rust's eager trait evaluation, the latter causing incredible amounts of pain beyond a certain complexity threshold.
How so? I'd really like to see an example. If you can't explain what requirements your function has before calling it then how do you know it even does what you expect?
I was investigating C++-style templates for a hobby language of mine and SFINAE is an important property to make them work in realistic codebases, but leads to exactly this problem. When a compile error occurs, there isn't a single cause, or even a linear chain of causes, but an potentially arbitrarily large tree of them.
For example, it sees a call to foo() and there is a template foo(). It tries to instantiate that but the body of foo() calls bar(). It tries to resolve that and finds a template bar() which it tries to instantiate, and so on.
The compiler is basically searching the entire tree of possible instantiations/overloads and backtracking when it hits dead ends.
Showing that tree as a tree makes a lot of sense.