-
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
Checked generics calling templates #2153
Comments
Since our interop/migration target is C++17, don't we need to assume that all C++ templates lack constraints? |
Regardless of what
I think Philosophically: when I write a generic function with constraint That to me suggests that (3) is the right option: we should only allow a generic to call a template if all of the template's constraints are satisfied by the generic's constraints. The reason is: if there is a constraint I think this would mean that a generic cannot call a template that has value constraints unless those value constraints are known to be satisfied regardless of the generic parameters. That said -- it's not clear to me that C++20 concepts can't be modeled symbolically in Carbon. Specifically, given a C++20 concept |
This was discussed today in open discussion There were questions about what happens if an operation is performed on a generic value with value constraints on it, as in:
This argument was found to be suspicious:
Reasoning: It seems like in general it is hard to determine whether conditions Options 1 and 2 were found to be very similar. In option 2, constraints would appear in the signature of a function. In option 1, those same constraints could be enforced in the first line of the function by calling a templated |
Recently option 3 has been discussed with the idea that value constraints would be named. A named value constraint would be called a "predicate." The only reasoning about constraints would be "type has the right set of predicates". Example:
|
In addition to what @josh11b mentions, a couple of other points that came up... One: the actual checking of these predicates are intrinsically "template" phase (during late-bound type checking). Before that, the only modeling is through the name itself as a symbolic predicate. Two: the utility of (3) is merely to facilitate propagating predicates from an inner (typically template) context where it is functionally required (the implementation of In essence, this is a tradeoff between early checking of predicates and enforced propagation once checked vs. trivially evolving templates (and intervening generics) to begin explicitly checking predicates that already happen to hold. The first provides some "shifted-left" developer experience benefits when writing code that fails to satisfy a predicate. The second provides some ease of deploying more strict explicitly checked predicates. My initial instinct is to prioritize propagating predicates and checking them earlier, as tightening these kinds of predicates tends to be more rarely done and an undertaking that can afford to work through various workaround techniques to manage the incremental rollout. But maybe I'm wrong about that. I think both models are workable, and it also seems conceivable for us to switch in the future. Although, it seems somewhat less work to start by enforcing predicates and then relax the rules than start w/o that enforcement and introduce it later. |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Discussed in open discussion today. |
I think both @zygoloid and I are happy with option (3). A key clarification here, is that constraints on a template binding are checked when initializing that binding, but after it is bound as a template, it becomes dependent and anything further is late checked. The result is that we don't need a special escape hatch, this comes with one built-in, but we expect it to look a bit different from what was described previously. The example of an escape hatch should be something like:
More details of this in the open discussion that @josh11b linked. Marking this as resolved since I think we have enough for leads consensus. Attempted capturing of rationale for the remaining issues discussed:
|
Continued from part 1: #3231. Second step updating `docs/design/generics/details.md`. There remains some work to incorporate proposal #2200. - The biggest changes are incorporating much of the text of proposals: - #2173 - #2687 - It incorporates changes from proposals: - #989 - #1178 - #2138 - #2200 - #2360 - #2964 - #3162 - It also updates the text to reflect the latest thinking from leads issues: - #996 - #2153 -- most notably deleting the section on `TypeId`. - Update to rule for prioritization blocks with mixed type structures from [discussion on 2023-07-18](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.7jxges9ojgy3) - Adds reference links to proposals, issues, and discussions relevant to the text. - Also tries to use more precise language when talking about implementations, to avoid confusing `impl` declaration and definitions with the `impls` operator used in `where` clauses, an issue brought up in - #2495 - #2483 --------- Co-authored-by: Richard Smith <[email protected]>
Question: Given a function
TemplateFunc
with a template parameter, defined either in Carbon or C++, when is it legal for a generic functionGenericFunc
to call it?Since
TemplateFunc
has a template parameter, it may have a monomorphization error, where the error is not detected whenTemplateFunc
is defined, but when it is called with a concrete type value forTT
. This is expected, since templates are specifically for the cases when the definition ofTemplateFunc
cannot be checked on its own (or C++ interop). Constraints (likeTC
) are not required on template functions, but having them, assuming they match the actual conditions for template instantiation to succeed, "shifts errors left / earlier in the call stack." This narrows down where the error could be, improving the user experience. Named constraints may be reused, avoiding repeating constraints and maybe getting out of sync.Background: Previously this question was investigated in #136 .
Note: I am assuming that there are two kinds of constraints:
C
", andOptions from most permissive to least:
GenericFunc
if the concreteGT
used to instantiateGenericFunc
does not satisfy the constraints onTT
. "Calling a template exposes you to monomorphization errors, even from generics."GC
may optionally include value constraints, includingTC
. This would giveGenericFunc
the option of includingTC
in the constraints onGT
. Non-generic callers ofGenericFunc
would validate the value constraints likeTC
. Generic callers ofGenericFunc
would either ignore value constraints likeTC
, or evaluate them at monomorphization time, potentially triggering monomorphization errors in the caller. Value constraints don't give additional capabilities onGT
in the body ofGenericFunc
. "Generic functions may optionally include template constraints to shift monomorphization errors left/earlier in the call stack."GC
, the constraints on the type ofx
passed toTemplateFunc
, includes all constraints inTC
. Value constraints would have to be propagated by generic types. This means generic functions would not have monomorphization errors from calling templates, but template constraints would have to be added to callers transitively. "Generic functions include constraints of every template it calls." Option: We could introduce an escape hatch where a value constraint may be asserted in a generic function, allowing a template function to be called at the cost of the assertion possibly failing at monomorphization time.Discussion about option 3:
N > 10 or N <= 10
would probably arise and without the escape hatch would end up cluttering generic signatures.Some care will be needed to handle cases where the condition on the generic is not the same as on the template, but I currently believe these cases are solvable. One case of this: the type parameters of the calling generics might be transformed before being passed to the template. For example,
GenericFunc
could pass a value of typeGT*
orVector(GT)
toTemplateFunc
, which affects what is written in the constraintGC
.Out of scope: There is some need to define an interface if there are multiple possible definitions with the same name that could be called, due to overloading or specialization. That is something to consider separately.
The text was updated successfully, but these errors were encountered: