Open josh11b opened 1 year ago
C++ Rules
No unqualified name lookup through extend
Dependent unqualified name lookup
Require disambiguation anytime it could look inside a template
FYI, one use case for base class templates is implementing types that have different APIs for different specializations, such as std::vector<bool>
. This might be modeled in Carbon as:
interface VectorSpecialization {
let BaseType:! type;
// anything else that might change with specializatoin
}
impl [forall T:! Type] T as VectorSpecialization {
class BaseType {
// default API if not specialized
}
}
impl bool as VectorSpecialization {
class BaseType {
// Vector(bool)-specific API;
fn Flip[addr self: Self*]();
// ...
}
}
class Vector(T:! type) {
extend base: (T as VectorSpecialization).BaseType;
// ...
}
I agree that in this case we generally aren't going to need to find members of BaseType
when doing unqualified lookup in the implementation of Vector(T)
methods, and callers of functions like Flip
are going to be in a qualified context, which argues against the "Dependent unqualified name lookup" option (and would make the "Require disambiguation anytime it could look inside a template" option more painful for Vector(T)
).
The alternative to this specialization approach would require accessing the Flip
method through a member of Vector(T)
, which is a bigger difference from C++.
@zygoloid @chandlerc I thought of an argument for a particular approach. Right now name lookup with templates follows the information accumulation principle. By this I mean:
Applying this same approach to this problem gives:
#2
it finds #1
.#2
is repeated at instantiation time, and finds #3
. This contradicts the previous assumption, and so results in an instantiation failure.So the cases are:
T.
qualification, and are looked up in the templated base at instantiation time. Those names are template dependent.I think that means that in all cases unqualified names are looked up at definition time, and based on the result they get a qualification. I think the remaining possible instantiation/monomorphization errors are all errors that could otherwise occur from template instantiation/monomorphizaton.
Example of the middle case:
interface I {
// #4
fn F();
}
class C2(template T:! I) {
extend base: T;
fn G[self: Self]() {
// #5
// At definition time, `F` resolves to #4 `I.F`
// based on the `I` bound on `T`.
F();
}
}
class B2 {
// #6
fn F();
impl as I {
// #7
fn F();
}
}
// Okay: `B2` implements `I`
var x2: C2(B2) = {};
// Monomorphization error: #5 resolved to `I.F` at
// definition time, which is #7 for `B2`, but #5
// resolves to #6 at instantiation time.
x2.G();
class B3 {
extend impl as I {
// #8
fn F();
}
}
// Okay: `B3` implements `I`
var x3: C2(B3) = {};
// Okay: #5 resolved to #8 at definition and
// instantiation time.
x3.G();
Summary of issue:
Consider this code:
What happens at
#2
inx.G()
? Does it call#1
?#3
? is it an error since both names are in scope?In discussion on 2023-09-05, we decided we were most interested in three options:
#2
always calls#1
.extend
-> always have to useSelf
orself
to get to base class names. In the example,#2
always calls#1
.#2
sees both#1
and#3
and either calls#3
or it is considered ambiguous. With other instantiations, likeC({})
,#2
might only see#1
and call that.#2
ambiguous at its definition and require qualification, independent of how it is instantiated. With this rule, changing a base class to be a template would break code in all transitively-derived classes.Details:
Note that switching
T
to a checked-genericT:! type
means name lookup no longer depends on the instantiation, and so there is a better option for avoiding this problem than C++.The difference between the first two options is what happens with non-templated base classes:
With C++ rules,
#4
would see#1
and#3
.With "no unqualified name lookup through
extend
",#4
would not consider#3
and instead always resolve to#1
. To call#3
, you would have to write something likeB.F()
,Self.F()
, orself.F()
. We might also consider addingbase.F()
or evenBase.F()
. With this rule, unqualified name lookup would only find names directly declared inclass
scope, and not in any referenced or nested scope.