Could multiple dynamic dispatch methods be bound to classes like traditional single dynamic dispatch methods?

Software Engineering Asked by Maggyero on July 23, 2020

C++ only supports single dynamic dispatch methods. Indeed, the following C++ program:

#include <iostream>

struct Shape {
  virtual void overlap(Shape* y) { std::cout << "Shape, Shape" << std::endl; }

struct Circle: Shape {
  void overlap(Shape* y) { std::cout << "Circle, Shape" << std::endl; }
  void overlap(Circle* y) { std::cout << "Circle, Circle" << std::endl; }

void overlap(Shape* x, Shape* y) { std::cout << "Shape, Shape" << std::endl; }
void overlap(Circle* x, Shape* y) { std::cout << "Circle, Shape" << std::endl; }
void overlap(Circle* x, Circle* y) { std::cout << "Circle, Circle" << std::endl; }

int main() {
  Shape* x = new Circle();
  Shape* y = new Circle();
  overlap(x, y);
  return 0;


Circle, Shape
Shape, Shape

If C++ supported multiple dynamic dispatch methods (i.e. bound to classes) and multiple dynamic dispatch functions (i.e. not bound to classes), the previous program would output:

Circle, Circle
Circle, Circle

Common Lisp, Dylan and Julia support multiple dynamic dispatch functions but do not support methods (single or multiple dynamic dispatch).

Would it be possible for a language to support multiple dynamic dispatch methods instead of or in addition to multiple dynamic dispatch functions?

I am asking this because supporting only multiple dynamic dispatch functions look like a step backward to procedural programming. Encapsulation (implementation hiding behind an interface) is a pillar of object-oriented programming. Using functions in place of methods forces the exposure of the supposedly hidden states of objects to be able to manipulate them, because contrary to methods, functions do not have a privileged access to these states.

2 Answers

It's always possible

There are lots of work-arounds to implement multi-methods in languages that don't support them:

  • Some use dispatch tables. The most impressing is imho in Alexandrescu's Modern C++ Design where template magic is used to make it happen almost naturally... provided that you're very versed in template programming.
  • Some hijack the build-in single dispatch to make a double-dispatch happen. All you need is a "bounce-back call" in which each class has the responsibility of performing one level of dispatching. Google for double-dispatch in your favourite language and you'll get it.

Since there are known solutions to implement it, it can for sure be automated and build into the language.

It's just matter of cost and sacrifice

  • Performance: the more dimensions in the dispatch, the slower will the dispatch be. It's either because of more indirections or because of complexer searches in larger dispatch tables.
  • Design: this design is not in the mind of the Open/Close principle since you'd need to add a new implementation when a new partner-class is derived: you have to know in advance the possible interactions.

The design sacrifice of both the Open/Close principle, but also the principle of least knowledge is in my view the main reason why there are not much languages that offer what you expect.

Your example

Take the example of your overlap() and imagine there would be a way to add multi-methods (i.e. methods in the class and not free-standaing functions):

  • Imagine that you have implemented the Shape Circle Triangle and Retangle.
  • Imagine even that you've found a nice trick to take advandage of the symetry of the problem.
  • Your classes are fine and delivered in a library. Now the user of your library adds a new Shape, for example Octopus: how would this interact with your library: the user will not be able to change your library (close) - how can he/she extend it?

Conclusion : the solution should remain outside of the class

In your example, finding the overlap is in fact not the concern of one single class, but of two classes together. Adding a new class that intervenes in this dynamic, requires to define new couples of possible interactions. So in the end the interaction belongs not to the class, but to the context that knows about the potential classes in presence.

And if you consider the problem under this angle, the existing solution already offered for multiple-dipatch in C# seems to be the most promising to go.

Correct answer by Christophe on July 23, 2020

This is basically a fundamentally impossible problem:

The fundamental idea of multiple dispatch is that all arguments that take part in the dispatch are equal, i.e. that there is no argument which is "special". So, if you don't want a "special" argument, then you only have two choices: you either have privileged access to no arguments, or you have privileged access to all arguments.

The former case is what you are complaining about, but the latter case means that you get privileged access to objects that you don't own, thus breaking object-oriented encapsulation.

The fundamental idea of object-oriented encapsulation is that an object only has privileged access to itself. It doesn't even have privileged access to other instances of the same type, unlike Abstract Data Types. (That, by the way, is why instances of classes in Java are not objects, only instances of interfaces are objects.)

So, by the very definition of Object-Orientation, there can only ever be at most one "special" argument.

I remember reading a research paper on adding Multimethods to single-dispatch OO languages using so-called Tuple Classes. The idea was that you can define a class something like this:

tuple struct (Circle x, Circle y) {
  void overlap() { std::cout << "Circle, Circle" << std::endl; }

But the whole point of doing it this way was that methods in the Tuple Class (Circle x, Circle y) can only interact with the public API of x and y precisely because they are not defined by the original authors of Circle and thus shouldn't have privileged access.

Otherwise, I could simply define a Tuple Class

tuple struct (DoesntMatterWhatIPutHere x, SuperSecureCreditProcessor l33th4X0r) {
  void emailMeThePins() { std::cout << l33th4X0r.getPin(); }

Answered by Jörg W Mittag on July 23, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP