从派生的variadic模板类调用基本模板的虚方法

时间:2014-10-15 03:41:54

标签: c++ templates c++11 polymorphism variadic-templates

这基本上是an earlier question的后续行动(不是由我提出,但我对答案感兴趣)。

问题是: 为什么编译器/链接器无法解析派生类对虚函数的调用? 在此例如,派生类是一个带有可变参数的模板类,它多次对同一个模板类应用多次继承(对于可变参数中的每个类型一次)。

在下面的具体示例中,派生类为JobPlant,并且正在从Worker调用它。调用抽象work()方法无法链接,同时调用workaround()链接并以预期方式执行。

这些是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用于演示解决方法。

Job是一个抽象基类,它具有关联的派生类。 Work是一个执行作业的抽象模板类。 Worker是一个标识JOB并执行它的模板(struct而不是class,纯粹是为了减少语法混乱):

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

JobPlant是由JOBS参数化的模板类,它会找到Worker来执行job。对于JobPlant中的每种作业类型,Work都会从JOBS继承。 MyJobPlantJobPlant的实例,并从关联的work抽象类中实现虚拟Work方法。

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

1 个答案:

答案 0 :(得分:9)

您明确调用p->Work<JOB>::work(),即Work<JOB>中的纯虚方法。这个方法没有实现(毕竟它是纯粹的)。

派生类可能已实现/覆盖该方法并不重要。 Work<JOB>::表示您需要该类的版本,而不是派生类的版本。没有动态调度。

Work<JOB>::work()是用于从派生类中的重写方法调用基类方法的语法,并且您确实需要基类方法。)


当您删除显式Work<JOB>::时,结果是歧义错误。尝试解析名称work时,编译器首先尝试确定哪个基类包含该名称。之后,下一步将是该类中不同work方法之间的重载决策。

不幸的是,第一步导致含糊不清:多个基类包含名称work。然后编译器永远不会试图找出匹配的重载。 (它们不是真正的重载,而是相互冲突,因为它们来自不同的类)。

通常这可以通过将基类方法名称带到using的派生类中来解决(或者它在技术上称为using所做的)。如果为基类的所有using方法添加work声明,编译器会在派生类中找到名称work(无歧义),然后可以继续进行重载解析(也是没有暧昧)。

可变参数模板使事情变得复杂,因为我认为using Work<JOBS>::work...;不合法(我的编译器也不这么认为)。但是如果你“手动”编写基类,所有的工作方法都可以进入最后的类:

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

使用此构造,有问题的p->work(dynamic_cast<const JOB &>(job)); compiles and runs successfully