2

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

#include <iostream>

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

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

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

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

outputs:

Circle, Shape
Shape, Shape

If C++ supported multiple dynamic dispatch methods (i.e. with privileged access to a single parameter, the message receiver) and multiple dynamic dispatch functions (i.e. without privileged access to any parameters), 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 looks like a step backward to procedural programming. Encapsulation (implementation hiding behind an interface) is the cornerstone 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
  • You seem concerned about access. What do you need that a friend function doesn’t provide? Commented Jul 22, 2020 at 3:47
  • @candied_orange Giving up methods for functions break encapsulation, which is my concern.
    – Géry Ogam
    Commented Jul 24, 2020 at 20:19

2 Answers 2

3

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.

12
  • Modern C++ Design is such a difficult book. Mr. Alexandrescu must be incredibly versed in C++.
    – Andy
    Commented Jul 22, 2020 at 10:27
  • @Andy It works like magic. But like magic, it's difficult to understand. This is why I preferred to give a warning: "...provided that you're very versed in template programming" ;-)
    – Christophe
    Commented Jul 22, 2020 at 10:33
  • Thanks for this quality answer Chris (as always). I think I disagree with your statement that multi-methods break the law of Demeter since the law is about communicating only with immediate entities (like method parameters). However I perfectly agree that multi-methods violate the open–closed principle which is about being open for extension and being closed for modification, thanks to the use of abstract classes instead of concrete classes—which is not the case with the Circle* parameter of my overlap method.
    – Géry Ogam
    Commented Jul 22, 2020 at 18:44
  • 1
    @Maggyero Yes, completely: the world is full of contradiction and we just have to find the right balance for the problems we are dealing with. Ideally the solution would be in multi-functions that rely on methods in order not to break encapsulation. However, this is not always possible, so we might from time to time have to rely on friends and privileged access. We then have to do it in a way to minimise the adverse impacts.
    – Christophe
    Commented Jul 22, 2020 at 18:57
  • 1
    @Maggyero: A good example is the concatenation of two doubly-linked lists. Doing it in O(1) requires privileged access to the next pointer of the tail of the first list and the prev pointer of the head of the second list. This is fundamentally impossible in OO. You cannot concatenate two doubly-linked lists in O(1) and at the same time observe object encapsulation. Cook calls operations that require inspection of two or more object representations at the same time "complex operations", and these are only allowed for Abstract Data Types, but not for Objects. Commented Jul 23, 2020 at 5:11
3

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(); }
}
8
  • Thanks Jörg for this interesting answer. You said "It doesn't even have privileged access to other instances of the same type." Are you talking about Java? Because this is not the case in C++ where members and friends of a type have privileged access to members of instances of the same type.
    – Géry Ogam
    Commented Jul 22, 2020 at 19:04
  • @Maggyero: This has nothing to do with the language. This is part of the definition of OO. In Java, the language specification calls both instances of classes and instances of interfaces "objects". But, those are only objects in the context of Java, going by the definition of OO, only instances of interfaces are actually objects. Instances of classes are Abstract Data Types, precisely because objects (in the Java sense) of the same class can call each other's private methods, whereas interfaces cannot define private methods, so as long as all references are typed as interfaces, all Commented Jul 22, 2020 at 19:09
  • "objects" are Objects. So, what you are basically saying is that instances of classes in C++ are not objects. Commented Jul 22, 2020 at 19:10
  • You are talking about "instances of interfaces", but I thought that interfaces (abstract classes) could not be instantiated, by definition.
    – Géry Ogam
    Commented Jul 22, 2020 at 19:18
  • 1
    IFoo foo = new Foo(). foo is an instance of IFoo, i.e. the reference to the object returned by new Foo() is typed as IFoo, and therefore, I can only access members that are defined in IFoo. Since interfaces do not allow the definition of state or private methods, this means that I can only use the public API of foo, and am therefore observing object-oriented encapsulation. If instead, I had Foo foo = new Foo(); Foo bar = new Foo(), then foo would be allowed to access bar's internal representation and private API and vice-versa, meaning they are not objects. Commented Jul 23, 2020 at 5:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.