Open zygoloid opened 3 months ago
The repetitiveness of p->(Base.F.NonVirtual(Base))()
is a bit of a concern for me. There are two different Base
s here with two different meanings -- one is where we look up F
and the other is the derived type whose implementation we're using (and there's the secret Base
we get from the type of p
). Reducing this a bit would be nice if we take this route -- perhaps something like p->(NonVirtual(Base).F)()
could work?
Summary of issue:
In C++, there is often a desire to make a non-virtual call to a virtual function -- for example when implementing a function in a derived class, it's sometimes desirable to call the base class implementation of the function.
How do we express this kind of pattern in Carbon?
Details:
In Carbon, given
... and
p: Base*
, we know:p->F()
performs virtual dispatchp->F()
means the same thing asp->(Base.F)()
Therefore in general, qualified and unqualified calls to virtual functions always perform virtual dispatch -- they're calls to the same callable objectBase.F
, and its implementation ofBindToRef
is (presumably) where virtual dispatch happens.So it seems that it is not straightforward for us to follow C++ and say that qualified calls don't do virtual dispatch, unless we make some kind of hole in our model for member binding operators or qualified name lookup that allows us to distinguish these cases. And it's not even clear that we'd want to: allowing virtual function calls to be forwarded and invoked indirectly is necessary for us to have a story compatible with C++'s story for pointers to members.
Some options:
Base.F
and a class derived fromBase
as input, and returns a callable. There are many options here; we could do this by overloadingBindTo*
(for examplep->(Base.F.(Base))()
) or adding a member function to the type of virtual functions (p->(Base.F.NonVirtual(Base))()
) or adding a member function to the type of bound virtual functions (p->F.NonVirtual(Base)()
-- though this would presumably first perform a vtable lookup then throw away the result, which seems suboptimal) or adding a free function (p->(NonVirtual(Base.F, Base))()
) or ...Base.F(p)
as a direct non-virtual call, andp->(Base.F)()
as a virtual call.BindToRef
that distinguish direct and indirect binding in some way. Eg,p->F
could mean something slightly different fromp->(Base.F)
orp->(f)
, with the former performing virtual dispatch and the latter not doing so.p->(Class.F)()
, and say that performs a non-virtual call, whereaslet f: auto = Class.F
andp->(f)()
would perform a virtual call.A related question is whether
Derived.F
is a different value fromBase.F
, or just findsF
in the extended base class. Theimpl fn F()
doesn't need to introduce a new, shadowingF
, just to implement the existingF
, if there is no situation whereDerived.F
behaves differently fromBase.F
.Any other information that you want to share?
A related concern is the behavior of
derived_p->base.F()
. Intuitively it seems like this might invoke the base class's version ofF
, but presumably won't:derived_p->base
is a reference expression naming aBase
object, soderived_p->base
should behave like*base_p
, and(*base_p).F()
should perform virtual dispatch, and soderived_p->base.F()
seems like it must also perform virtual dispatch, unless we add some kind of special case.