Thread overview
What are virtual functions?
Apr 14, 2021
Berni44
Apr 14, 2021
Paul Backus
Apr 14, 2021
FeepingCreature
Apr 18, 2021
ShadoLight
Apr 19, 2021
Alain De Vos
Apr 19, 2021
FeepingCreature
April 14, 2021

I'm trying to understand, what virtual functions are. I found the specs, but I can't make head or tail of it.

  • What is a vtbl[]? Obviously a function pointer table. But where to find this? The examples don't use it. Maybe something inside of the compiler?
  • Which of the eight functions in the example are virtual and and which not? OK B.abc is said to be virtual, as the comment states. But it seems never to be used. And why is it considered to be virtual?
  • There is also the term "covariant function", which is not explained. What is this?

I'm asking, because I'm currently writing new docs for std.format. The current (stable) docs of formatValue tell, that some toString versions are discouraged, but not for virtual functions. I would like to understand the reason for that, so I can put that properly into the new docs. The reasons, why it's better to not use these versions in normal functions is explained in the changelog. But I miss the reason, why virtual functions should still use them; probably, because I did not understand, what that is.

Can you help me?

April 14, 2021

On Wednesday, 14 April 2021 at 13:43:20 UTC, Berni44 wrote:

>

I'm asking, because I'm currently writing new docs for std.format. The current (stable) docs of formatValue tell, that some toString versions are discouraged, but not for virtual functions. I would like to understand the reason for that, so I can put that properly into the new docs. The reasons, why it's better to not use these versions in normal functions is explained in the changelog. But I miss the reason, why virtual functions should still use them; probably, because I did not understand, what that is.

The recommended toString versions are templates, but virtual functions can't be templates (because you can't have a function pointer that points to a template). So, the non-template versions are still considered acceptable for cases when toString has to be virtual--i.e., when you're overriding Object.toString.

April 14, 2021

On Wednesday, 14 April 2021 at 13:43:20 UTC, Berni44 wrote:

>

I'm trying to understand, what virtual functions are. I found the specs, but I can't make head or tail of it.

  • What is a vtbl[]? Obviously a function pointer table. But where to find this? The examples don't use it. Maybe something inside of the compiler?
  • Which of the eight functions in the example are virtual and and which not? OK B.abc is said to be virtual, as the comment states. But it seems never to be used. And why is it considered to be virtual?
  • There is also the term "covariant function", which is not explained. What is this?

Recommended reading: https://en.wikipedia.org/wiki/Liskov_substitution_principle

This is all related to object-oriented programming and class inheritance. Because we can put a subclass object into a superclass variable (class Child : Parent { }; Parent parent = new Child;), we cannot look at the type of an object variable to decide which methods to call, because the object itself may be of a subtype. As such, when we call a method foo on Parent, the compiler looks up the class info in a pointer in the first 8 bytes of the object, finds the method pointer for foo, and calls it with the object as a hidden parameter. (This is the this field.)

So a virtual method is a method that is called "virtually", as compared to directly by name, by turning the method name into a function pointer call via the classinfo.

The list of function pointers for methods in the class info is called the virtual method table, or vtable.

Covariance is related to the Liskov principle, and just means that because Child can be treated as a Parent, a method that returns Parent in the superclass can be overridden (its vtable pointer replaced with a new one) by one that returns a Child in the subclass. In other words, as "Child class replaces Parent class", the "return type Child" can replace the "return type Parent"; ie. in the child class you can use a child class of the return type, ie. they "vary together" - covariance.

The opposite (contravariance) happens for parameters: if a superclass method takes Child, the subclass can take Parent instead - again, because Child can turn into Parent per Liskov.

A different way to think about this is that method parameter and return types form a contract that is defined by the superclass and fulfilled by the subclass, and the subclass can relax the call contract ("I demand from my caller") and restrict the return contract ("I promise my caller"). Since the Child, by Liskov, can do everything the Parent can do, demanding less - ie. a Parent instead of a Child - keeps the superclass's call contract valid, and promising more - ie. returning a Child instead of a Parent, which may have additional capabilities - keeps the superclass's return contract valid.

April 18, 2021

On Wednesday, 14 April 2021 at 14:06:18 UTC, FeepingCreature wrote:

>

On Wednesday, 14 April 2021 at 13:43:20 UTC, Berni44 wrote:
[..]
Covariance is related ...
[..]

The opposite (contravariance) happens ...
[..]

Nice answer but, just to be clear - D only supports covariance on return types at the moment, and doesn't support contravariance on parameters, right?

I remember contravariance being periodically requested in the past but, AFAICR, it has not been implemented, right? A quick search through the forums didn't turn anything up either... and this does not compile:

class A {}
class B : A {}

class Y {
	public void bar (B b) {}
}
class X : Y {
	public override void bar (A a){}
}
April 19, 2021

Also three words are used in this context.
Static binding, dynamic binding, late binding.

April 19, 2021

On Sunday, 18 April 2021 at 23:04:26 UTC, ShadoLight wrote:

>

On Wednesday, 14 April 2021 at 14:06:18 UTC, FeepingCreature wrote:

>

On Wednesday, 14 April 2021 at 13:43:20 UTC, Berni44 wrote:
[..]
Covariance is related ...
[..]

The opposite (contravariance) happens ...
[..]

Nice answer but, just to be clear - D only supports covariance on return types at the moment, and doesn't support contravariance on parameters, right?

I remember contravariance being periodically requested in the past but, AFAICR, it has not been implemented, right? A quick search through the forums didn't turn anything up either... and this does not compile:

class A {}
class B : A {}

class Y {
	public void bar (B b) {}
}
class X : Y {
	public override void bar (A a){}
}

... That doesn't work?! Holy hell. I mean, run.dlang.io confirms, but ... why not?! If you already support return type covariance, parameter contravariance should be easy. It's the same thing! You don't need to do anything! Any B is directly a valid A! Like, sure, there's versions of this that don't trivially work, like mixing interface and class parents, but direct superclass contravariance should be easy. ... Weird. I don't get it.

Reading https://issues.dlang.org/show_bug.cgi?id=3075 seems like it collides with overloading. Except ... it doesn't. Because you can't implement multiple overloaded methods with contravariance in a subclass, because it doesn't work for interfaces anyway (cause they occupy a different interface slot in the class and so aren't strictly Liskov.) So the selection of the function to override is still unambiguous.

I don't get it.