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);
}