I'd like to downcast a pointer to a member function, where the base class is inherited virtually. This leads to an error at compile time.
In my application, I have a collection of several objects (e.g. D1
, D2
), that have a common base class B
. They are held together and kept track of by means of a std::vector<B*>
.
The derived objects introduce double
-returning functions that need to be called on a regular basis. The base class keeps track of these functions by means of a std::vector<double(B::*)(void)>
. The whole chain is utilized in the second to last line of the main function.
Therefore, I needed to cast the function pointers when adding them to the vector, as is done in the constructor of the derived classes D1
and D2
. his is successful in case of D1
, but not in case of D2
, as B
is a virtual base of D2
. As I learned, a static cast does not work in this case (error message 1), because the deriving classes D21
and D22
only have a pointer to the instance of B
. However, a dynamic_cast also leads to an error at compile time (error message 2), saying that the target is not of pointer type.
error message 1: error: pointer to member conversion via virtual base 'B' v.push_back(static_cast(&D2::f2));
error message 2: error: cannot dynamic_cast '&D2::f2' (of type 'double (class D2::)()') to type 'double (class B::)()' (target is not pointer or reference)
I'd like to know how, if at all, I can perform the desired cast. If not I'd like to know why such a thing is not possible.
Edit: Some background: I integrate a system of ordinary differential equations numerically. The derived classes represent the individual equations of the system, and each derived class computes its own state. By collecting the classes in the vector o
, I can quickly modify the system of equations. The vector v
in the base class is needed for output purposes. During integration, each subsystem also takes care of its own output through the base class. The diamond arose because I separated two often used pieces of code into two individual classes.
Arguably, there are other ways to design the class hierarchy, and there are other ways to achieve my goal (in this case I made the function p
in B
virtual, and re-implemented it in the deriving classes). However, I could not fathom why the cast failed, so I asked the question.
MWE:
#include <vector>
#include <algorithm>
#include <iostream>
// Base class
class B {
protected:
std::vector<double(B::*)(void)> v;
public:
void p ( void )
{for_each(v.begin(), v.end(), [this] (double (B::*f) (void)) {std::cerr << (this->*f)() << std::endl;});};
};
// Normal inheritance
class D1: public B
{public:
double f1 ( void ) { return 1; }
D1() { v.push_back(static_cast<double(B::*)(void)>(&D1::f1));} // Casting is successful here.
};
// Setting up 'the diamond'
class D21: virtual public B {};
class D22: virtual public B {};
class D2: public D21, public D22
{public:
double f2 ( void ) { return 2; }
D2() { v.push_back(dynamic_cast<double(B::*)(void)>(&D2::f2)); } // How to cast here?
};
int main ()
{
// Vector holding the classes together
std::vector<B*> o;
// Set up the system
D1 d1;
D2 d2;
o.push_back(&d1);
o.push_back(&d2);
// Derived functions are called
for_each(o.begin(),o.end(),[] (B *o) {o->p();});
return 0;
}
I believe it should be possible to implement the cast that you tried to use. However, it is an especially difficult corner case, which is likely the reason why it's not allowed:
You can think of member pointers as offsets into the object (non-virtual case) or into the vtable (virtual case). This works fine as long as the relevant layout of the object is fixed. However, with a virtual base, the position of the virtual base within the object may depend on the concrete subclass. More specifically, a class derived of D2
may have its virtual base at a different offset than D2
itself.
Now, when you cast a member pointer to a pointer relative to one of its base classes, the pointer needs to be adjusted. This is usually a simple addition of the relative offsets, which is a compile time constant. But since the location of the virtual base within D2
is not known at compile time, the adjustment would need to first discover this offset dynamically.
As I said, this is not impossible to implement. However, it adds significant complexity to the pointer adjustment code in the compilers, just to support an exceedingly rare corner case. And I guess that's the reason why it's not allowed.