The expression problem is about dynamic dispatch, not static dispatch. For example, you call a function F that returns an object of one of the relevant types, and another function G that returns one of the relevant operations, and then you apply the operation to the object. How can you have F extend the set of types, and also G extend the set of operations, without having to recompile the caller, or without F and G having to coordinate, and also avoid a runtime error due to a missing implementation of some operation for some type.
I agree (although others in this thread do not), but the comment I was responding to was claiming that in C++ operator overloading requires inheritance and virtual methods, and I was just pointing out that this is not true.
The comment you responded to doesn’t mention overloading.
> In C++ this can be done when very verbose inheritance and virtual calls
Virtual methods overload the corresponding method in the parent class they are inheriting from. It could be a named method like "add()", or an operator like "+()".
The author of that comment is implicitly assuming that all types are derived via inheritance from a common base type that defines all the (virtual) methods/operators being overloaded.
The operations are different methods, not overloads of each other, and the different types are the types on which the methods are defined, not arguments to the methods. That’s also how the article presents it. There are no overloads in the scenario.
The article presents examples in many languages. His C++ inheritance/override example was an example of how NOT to do it, since if you add a new virtual method to the base class then all the derived classes either have to use that base class method or be modified to override it in an appropriate way.
Overuse of inheritance is one of the ways to shoot yourself in the foot with C++. If you want to overload (apply same operator to multiple types - related or not), then just overload - don't use inheritance.
Your own example, above (B defines new type, C defines new operator) doesn't appear to be about inheritance at all - you don't even say what language you are talking about, and elsewhere you say (contracticting the article author) that this "expression problem" only applies to dynamic languages ... you seem to be all over the map on this one!
I’m sorry, but the expression problem is a fairly well-known problem, and you don’t seem to be familiar with it, so are drawing the wrong conclusions.
The problem statement is: You have code (e.g. a library) that supports polymorphic operations on a range of types. It is called the “expression problem” because the prototypical example is to have a type representing an expression (like an arithmetic expression) that can have various forms (subtypes, not necessarily in the OOP sense), and there are operations like evaluating the expression or printing the expression, which of course need to be implemented for all those subtypes (you need to know how to print a constant, how to print a binary expression, and so on).
There is code that can take an arbitrary expression whose exact subtype will only be known at runtime, and which invokes one of the available operations on the expression object.
Now, you want to be able to write secondary libraries (or programs) that extend the first library by extending the set of subtypes and/or the set of operations, but without modifying the first library (and without duplicating code from the first library). With OOP inheritance, adding new subtypes is easy. With the visitor pattern (or equivalent runtime case-distinction mechanisms like pattern matching in functional languages), adding new operations is easy. But the combination of both is difficult, if not unsolvable.
Overloading doesn’t apply, because overloading would dispatch on the static type of the expression variable, but the use case is that this is an expression parsed from a textual representation at runtime (e.g. in the context of an interpreter or compiler, or maybe some data format parser), so the variable holding the expression object has a generic type and could be holding any expression.
I'm just going by the article this thread is responding to. Perhaps the author's C++ example that he lead with was meant to be a strawman "look how bad C++ is (when we use it this way)"?
If the problem statement is just how to add operators and types to a dynamically typed expression (in some arbitrary language), then in addition to implementing any new operator overloads, there is the question of how to dispatch, which would most obviously be done by mapping (operator, operand type) pairs to the registered type-specific operator implementation (aka overload). Again, not sure what the big deal is here.
Virtual methods and overloading are not the same thing.
You are likely mixing up the term overload with the term override.
Apples and oranges.
Mechanism vs functionality.