-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Constrained template name lookup #949
Comments
Note that I excluded an option that no longer had any supporters:
|
My current preferences are for "type", for simplicity and consistency with C++, or "union minus conflicts", for evolvability and convenience. After that I'd consider "constraint over type", and in last place for me is "constraint". My concerns with "constraint" is that it has a lot of overlap with generics and for those use cases I think we should be pushing people to generics. It also gives up too much in the way of continuity and incrementality without gaining evolvability. I thinks its main benefit is disambiguation, which isn't as important to me in a template context. |
I'm still somewhat struggling with "type" because the constraint in a type-of-type position in that model actively confuses my thinking about it... But I'm being won over by both constraint over type and union minus conflicts. I think I'd be quite happy with either of those at this point. Beyond the advantages over "constraint" that @josh11b mentions, they also both have the nice property that the unconstrained point is actually useful and exactly matches unconstrained C++ templates. As the overwhelming majority of C++ templates are unconstrained (constraint syntax is very new, and just hasn't had time for widespread adoption), this seems like the most important bit of consistency with C++ for me. |
The "union minus conflicts" model, particularly if we look in the impl of the constraint (which we can because it's a template) to ensure aliases don't form conflicts, seems really convenient and user friendly to me. I think that's the one I'm somewhat leaning toward at this point, but still mulling over. |
ConstraintI think this rule largely removes the difference between constrained templates and generics. Some differences would probably remain (eg, Union minus conflicts vs constraint over typeThe difference between these two is whether you look in the constraint or reject in the case of ambiguity. Given that choice, I think I prefer rejecting, because I expect there to be situations where it's unclear whether a constraint is known or not when type-checking a template, and in such circumstances the "constraint over type" model would give quite different interpretations for programs that differ in ways that we might not want a developer to need to understand. For example:
What happens here, under "constraint over type"? I think:
... and I think it's not reasonable to expect a Carbon developer to know whether type-checking the template will be able to figure out which overload is picked here. In contrast, under "union minus conflicts", we will never pick So, of these two, I think I prefer "union minus conflict" for that reason, and I'd be happy removing "constraint over type" from consideration. Type versus other rulesThe "type" rule is simple and matches C++ behavior, but sacrifices consistency with the behavior of generics, and means that constraints don't affect name lookup, which may be surprising. However, in a constrained template that correctly deals with conflicts, all uses of members from the constraint must always be written with explicit qualification anyway, so the "type" rule doesn't add any additional burden for such templates. Moreover, if we consider explicit qualification by the constraint to be good style (because it avoids ambiguity problems in the case where there is a type versus constraint conflict), the "type" rule encourages that good style. My conclusionsBoth the "union minus conflict" and "type" rules have appeal. "Type" seems simpler but requires more work to use methods from interfaces in a constrained generic, and provides a less good story for migrating from templates to generics; "union minus conflict" provides better ergonomics for pragmatic cases where conflicts aren't expected. Of these, I'm leaning towards preferring "union minus conflict". |
I agree both with the analysis and conclusion FWIW. Maybe we have a decision? |
Seems Richard and I are both aligned here, and this doesn't seem like a contentious issue across the leads so let's call this decided with union-minus-conflicts as described above and with Richard's rationale. |
Support for member access expressions with syntax `container.member`, covering cases such as: * `object.field` * `object.method(args)` * `package.namespace.class.member` * `object.(interface.member)` * `object.(class.member)` ... and so on. Includes the rule for template name lookup as decided in #949. Co-authored-by: josh11b <[email protected]>
Implemented in #989. |
Support for member access expressions with syntax `container.member`, covering cases such as: * `object.field` * `object.method(args)` * `package.namespace.class.member` * `object.(interface.member)` * `object.(class.member)` ... and so on. Includes the rule for template name lookup as decided in #949. Co-authored-by: josh11b <[email protected]>
The strategy that we use for now to support template instantiation is to check the impl declaration as if it were a generic, but to defer all checking of the impl definition until we see a use in which all template parameters have arguments. At that point, we clone the impl definition and type-check the whole thing, with constant values set on the template parameters corresponding to the given arguments. No caching of template instantiations is performed yet; each time we form a reference to a template instantiation, we instantiate it afresh. We also don't implement the name lookup rule from #949 yet; lookups during template instantiation look only in the actual type and not in the constraint. Depends on #2699
Given a template function constrained to taking types implementing the interface
Hashable
:and we call it with a value with type
Potato
that implementsHashable
externally and has a method name conflict withHash
:Question: How should name lookup in the template function work?
Goals: These are desirable properties, but are in conflict. We won't be able to achieve all of these:
Options: Given this template function, which calls are legal and what do they resolve to?
&
for combining constraints. Conflict is both defining the name, with different definitions. Good for: continuity, incrementality, evolvability, convenience. Medium for substitutability. Bad for: disambiguation, simplicity. In addition to being a more complicated rule to explain, it has edge cases regarding when it triggers with respect to associated types.Type only
Conflict
Constraint only
I've omitted the "both" column with the case where the name is present in both the type and the constraint with the same definition. All options allow that name to be referenced unqualified to pick up the consistent definition.
The text was updated successfully, but these errors were encountered: