carbon-language / carbon-lang

Carbon Language's main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)
http://docs.carbon-lang.dev/
Other
32.24k stars 1.48k forks source link

how to make a non-virtual call to a virtual function? #4109

Open zygoloid opened 3 months ago

zygoloid commented 3 months ago

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.

class Base {
public:
  virtual void f() { ... }
};
class Derived : public Base {
public:
  virtual void f() override {
    // ... Derived things
    Base::f();
    // ... Derived things
  }
};

How do we express this kind of pattern in Carbon?

Details:

In Carbon, given

base class Base {
  virtual fn F[self: Self]() { ... }
}

... and p: Base*, we know:

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:

A related question is whether Derived.F is a different value from Base.F, or just finds F in the extended base class. The impl fn F() doesn't need to introduce a new, shadowing F, just to implement the existing F, if there is no situation where Derived.F behaves differently from Base.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 of F, but presumably won't: derived_p->base is a reference expression naming a Base object, so derived_p->base should behave like *base_p, and (*base_p).F() should perform virtual dispatch, and so derived_p->base.F() seems like it must also perform virtual dispatch, unless we add some kind of special case.

zygoloid commented 3 months ago

The repetitiveness of p->(Base.F.NonVirtual(Base))() is a bit of a concern for me. There are two different Bases 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?