You can make Base be a class template that takes its function pointers from its template argument:
extern "C" {
struct CStruct
{
void (*funA)(int, char const*);
int (*funB)(void);
};
}
template <typename T>
class Base
{
public:
CStruct myStruct;
void FillPointers() {
myStruct.funA = &T::myFunA;
myStruct.funB = &T::myFunB;
}
Base() {
FillPointers();
}
};
Then, define your derived classes to descend from an instantiation of Base using each derived class as the template argument:
class Derived1: public Base<Derived1>
{
public:
static void myFunA(int, char const*) { }
static int myFunB() { return 0; }
};
class Derived2: public Base<Derived2>
{
public:
static void myFunA(int, char const*) { }
static int myFunB() { return 1; }
};
int main() {
Derived1 d1;
d1.myStruct.funA(0, 0);
d1.myStruct.funB();
Derived2 d2;
d2.myStruct.funA(0, 0);
d2.myStruct.funB();
}
That technique is known as the curiously recurring template pattern. If you neglect to implement one of the functions in a derived class, or if you change the function signature, you’ll get a compilation error, which is exactly what you’d expect to get if you neglected to implement one of the pure virtual functions from your original plan.
The consequence of this technique, however, is that Derived1 and Derived2 do not have a common base class. The two instantiations of Base<> are not related in any way, as far as the type system is concerned. If you need them to be related, then you can introduce another class to serve as the base for the template, and then put the common things there:
class RealBase
{
public:
CStruct myStruct;
};
template <typename T>
class Base: public RealBase
{
// ...
};
int main()
RealBase* b;
Derived1 d1;
b = &d1;
b->myStruct.funA(0, 0);
b->myStruct.funB();
Derived2 d2;
b = &d2;
b->myStruct.funA(0, 0);
b->myStruct.funB();
}
Beware: Static member functions are not necessarily compatible with ordinary function pointers. In my experience, if the compiler accepts the assignment statements shown above, then you can at least be confident that they’re compatible for that compiler. This code isn’t portable, but if it works on all the platforms you need to support, then you might consider it “portable enough.”