This is essentially a follow-up to an earlier question (not posed by me, but I am interested in an answer).
The question is: Why does the compiler/linker fail to resolve the call to the virtual function from the derived class? In this case, the derived class is a template class with variadic parameters that applies multiple inheritance against the same template class multiple times (once for each type in the variadic parameters).
In the concrete example below, the derived class is JobPlant
, and it is being called from Worker
. Invoking the abstract work()
method fails to link, while invoking workaround()
links and executes in the expected way.
These are the link failures as shown by ideone:
/home/g6xLmI/ccpFAanK.o: In function `main':
prog.cpp:(.text.startup+0x8e): undefined reference to `Work<JobA>::work(JobA const&)'
prog.cpp:(.text.startup+0xc9): undefined reference to `Work<JobB>::work(JobB const&)'
prog.cpp:(.text.startup+0xff): undefined reference to `Work<JobC>::work(JobC const&)'
collect2: error: ld returned 1 exit status
Follow this link for demonstration of the workaround working.
Job
is an abstract base class, and it has associated derived classes. Work
is an abstract template class that performs a job. Worker
is a template that identifies the JOB
and performs it (struct
instead of class
purely to reduce syntax clutter):
struct Job { virtual ~Job() {} };
struct JobA : Job {};
struct JobB : Job {};
struct JobC : Job {};
template <typename JOB>
struct Work {
virtual ~Work() {}
virtual void work(const JOB &) = 0;
void workaround(const Job &job) { work(dynamic_cast<const JOB &>(job)); }
};
template <typename PLANT, typename... JOBS> struct Worker;
template <typename PLANT, typename JOB, typename... JOBS>
struct Worker<PLANT, JOB, JOBS...> {
bool operator()(PLANT *p, const Job &job) const {
if (Worker<PLANT, JOB>()(p, job)) return true;
return Worker<PLANT, JOBS...>()(p, job);
}
};
template <typename PLANT, typename JOB>
struct Worker<PLANT, JOB> {
bool operator()(PLANT *p, const Job &job) const {
if (dynamic_cast<const JOB *>(&job)) {
p->Work<JOB>::work(dynamic_cast<const JOB &>(job));
//p->Work<JOB>::workaround(job);
return true;
}
return false;
}
};
A JobPlant
is a template class parameterized by JOBS
, that finds a Worker
to perform a job
. The JobPlant
inherits from Work
for each job type in JOBS
. MyJobPlant
is an instance of JobPlant
, and implements the virtual work
methods from the associated Work
abstract classes.
template <typename... JOBS>
struct JobPlant : Work<JOBS>... {
typedef Worker<JobPlant, JOBS...> WORKER;
bool worker(const Job &job) { return WORKER()(this, job); }
};
struct MyJobPlant : JobPlant<JobA, JobB, JobC> {
void work(const JobA &) { std::cout << "Job A." << std::endl; }
void work(const JobB &) { std::cout << "Job B." << std::endl; }
void work(const JobC &) { std::cout << "Job C." << std::endl; }
};
int main() {
JobB j;
MyJobPlant().worker(j);
}
Derived classes do not have to implement all virtual functions themselves. They only need to implement the pure ones. That means the Derived class in the question is correct.
No, template member functions cannot be virtual.
A virtual function cannot be global or static because, by definition, a virtual function is a member function of a base class and relies on a specific object to determine which implementation of the function is called. You can declare a virtual function to be a friend of another class.
You explicitly call p->Work<JOB>::work()
, that is, the pure virtual method in Work<JOB>
. This method is not implemented (it's pure after all).
It doesn't matter that derived classes might have implemented/overridden that method. The Work<JOB>::
says that you want the version from that class, not something from a derived class. No dynamic dispatch happens.
(Work<JOB>::work()
is the syntax you would use to call a base class method from a overriding method in a derived class, and there you really want the base class method.)
When you remove then explicit Work<JOB>::
, the result is an ambiguity error. When trying to resolve the name work
, the compiler first tries to determine which of the base classes contains that name. After that, the next step then would be overload resolution amongst the different work
methods in that class.
Unfortunately the first step results in ambiguity: more than one base class contains the name work
. The compiler then never tries to figure out the matching overload. (They are not really overloads but conflicting with each other, since they are from different classes).
Usually this can be solved by bringing the base class method names into the derived class with using
(or however it's technically called what using
does). If you add using
declarations for all the work
methods of the base classes, the compiler finds the name work
in the derived class (no ambiguity) and can then proceed with overload resolution (also not ambigious).
The variadic template complicates things since I don't think using Work<JOBS>::work...;
is legal (and my compiler doesn't think so either). But if you compose the base classes "manually", all the work methods can be brought into the final class:
template <typename JOB, typename... JOBS>
struct CollectWork : Work<JOB>, CollectWork<JOBS...> {
using Work<JOB>::work;
using CollectWork<JOBS...>::work;
};
template <typename JOB>
struct CollectWork<JOB> : Work<JOB> {
using Work<JOB>::work;
};
template<typename... JOBS>
struct JobPlant : CollectWork<JOBS...> {
using CollectWork<JOBS...>::work;
typedef Worker<JobPlant, JOBS...> WORKER;
bool worker(const Job &job) { return WORKER()(this, job); }
};
With this construct, the problematic p->work(dynamic_cast<const JOB &>(job));
compiles and runs successfully.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With