Is it possible to perform double dispatch with runtime polymorphism?
Say I have some classes, and some of those classes can be added/multiplied/etc., and I want to store those dynamically within another class that performs type erasure at runtime. Then say I want to perform basic operations on the data held within that class.
The way to handle this (as far as I'm aware) is to use double dispatch to specialize the operation. However, all of the solutions I have encountered rely on the fact that you have a numerable amount of types, and then use virtual function calls or dynamic_cast
s, if-else
, and RTTI to deduce the type at runtime. Because the data held within the class isn't known until runtime, I can't create a bunch of virtual methods or do a brute force check on the types. So I figured the visitor pattern would be the best solution, but even then, I can't seem to get whether or not this is possible.
I have a wrapper class that holds a smart pointer to a nested polymorphic class to implement the type erasure and runtime polymorphism, but I can't figure out if it's possible to use the visitor pattern to do double dispatch on this.
Note that the code below is incomplete, it just shows my thought process.
class Wrapper {
private:
class Concept;
template<typename T> class Model;
class BaseVisitor {
public:
virtual ~Visitor() = default;
virtual void visit(Concept &) = 0;
};
template<typename T>
class Visitor : public BaseVisitor {
private:
T first_;
public:
Visitor(T first) : first_(first) {}
virtual void visit(Concept &other) override {
// perform addition
}
};
class Concept {
public:
virtual ~Concept() = default;
virtual void add(Concept &m) const = 0;
virtual void accept(BaseVisitor &visitor) const = 0;
};
template<typename T>
class Model final : public Concept {
private:
T data_;
public:
Model(T m)
: data_(m) {}
virtual void add(Concept &m) const override {
Visitor<T> v(data_);
m.accept(v);
};
virtual void accept(BaseVisitor &visitor) const override {
visitor.visit(*this);
};
};
std::shared_ptr<const Concept> ptr_;
// This isn't right, it just illustrates what I'm trying to do.
// friend Something operator+(Wrapper &lhs, Wrapper &rhs) {
// return (*lhs.ptr_).add(*rhs.ptr_);
// }
public:
template<typename T>
Wrapper(T value) : ptr_(std::make_shared<Model<T>>(value)) {}
};
I've looked into implementing double dispatch using function pointers, template specialization, and static type IDs as well, but I can't seem to figure out how to make it work.
Is this even possible?
EDIT
Based on the comments below, in order to be more specific and to give a little more background, I am using templated classes that use template functions to perform basic operations like addition and multiplication. However, I would also like to store those templated classes within a vector, hence the type erasure. Now, if I wanted to do operations on those classes after I perform the type erasure, I need some way to deduce the type for the templated function. However, since I can't easily get the internal held type back from the Wrapper
, I am hoping that there is a way I can call the correct template function on the data held within the Wrapper::Model<T>
class, whether that is a visitor pattern, static type IDs, whatever.
To be even more specific, I am working with classes to do delayed evaluation and symbolic computations, meaning I have classes such as Number<T>
, which can be Number<int>
, Number<double>
, etc. and classes such as Variable
, Complex<T>
and all of the TMP combinations for various operations, such as Add<Mul<Variable, Variable>, Number<double>>
, etc.
I can work with all of these fine at compile-time, but then I need to be able to store these in a vector -- something like std::vector<Wrapper> x = {Number<int>, Variable, Add<Number<double>, Variable>};
. My best guess at this was to perform type erasure to store the expressions inside the polymorphic Wrapper
. This serves double-duty to enable runtime parsing support of symbolic expressions.
However, the functions I wrote to handle the addition, such as
template<typename T1, typename T2>
const Add<T1, T2> operator+(const T1 &lhs, const T2 &rhs)
{ return Add<T1, T2>(lhs, rhs); }
can't accept Wrapper
and pull the type out (due to the type erasure). I can, however, insert the Wrapper
into the Add
expression class, meaning I can carry around the hidden types. The problem is when I actually get down to evaluating the result of something like Add<Wrapper, Wrapper>
. In order to know what this comes out to, I'd need to figure out what's actually inside or to do something along the lines of double dispatch.
The main problem is that the examples for double dispatch that most closely match my problem, like this question on SO, rely on the fact that I can write out all of the classes, such as Shapes
, Rectangles
. Since I can't explicitly do that, I'm wondering if there's a way to perform double dispatch to evaluate the expression based on the data held inside the Model<T>
class above.