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

jxh*_*jxh 13 c++ polymorphism templates variadic-templates c++11

这基本上是对早期问题的跟进(不是我提出的,但我对答案很感兴趣).

问题是: 为什么编译器/链接器无法解析派生类对虚函数的调用?在这种情况下,派生类是具有可变参数的模板类,该参数对同一模板类多次应用多次继承(对于可变参数中的每个类型一次).

在下面的具体示例中,派生类是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
Run Code Online (Sandbox Code Playgroud)

请点击此链接以演示变通方法的工作原理.

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

A JobPlant是一个参数化的模板类JOBS,它找到一个Worker执行a 的模板类job.在JobPlant从继承Work于各种作业类型JOBS.MyJobPlant是一个实例JobPlant,并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);
}
Run Code Online (Sandbox Code Playgroud)

sth*_*sth 9

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

派生类可能已实现/重写该方法并不重要.该Work<JOB>::说,你想从这个类,而不是从一个派生类的东西的版本.没有动态调度.

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


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

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

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

可变参数模板使事情复杂化,因为我认为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); }
};
Run Code Online (Sandbox Code Playgroud)

使用此构造,有问题的p->work(dynamic_cast<const JOB &>(job)); 编译并成功运行.

  • 通过以下代码替换'p-> Work <JOB> :: work()'也可以解决问题.工作<JOB>&w =*p; w.work(dynamic_cast <const JOB&>(job)); (2认同)