从派生可变模板类调用基本模板的虚方法 [英] Calling virtual method of base template from derived variadic template class

查看:137
本文介绍了从派生可变模板类调用基本模板的虚方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这本质上是先前的问题的后续追踪(不是由我提出,但我对某个问题是: 为什么编译器/链接器无法解析对虚函数的调用在这种情况下,派生类是具有可变参数的模板类,它对同一模板类多次应用多重继承(对可变参数中的每个类型应用一次)。 / p>

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



这些链接失败如 ideone 所示:

  /home/g6xLmI/ccpFAanK.o:在main函数中:
prog.cpp :( .text.startup + 0x8e) :未定义的引用`Work< JobA> :: work(JobA const&)'
prog.cpp :( .text.startup + 0xc9):未定义引用`Work< JobB& )'
prog.cpp :( .text.startup + 0xff):未定义对`Work< JobC> :: work(JobC const&)'的引用
collect2:error:ld returned 1 exit status

关注此链接

作业是一个抽象基类,它具有相关的派生类。 工作是执行作业的抽象模板类。 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 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 是一个模板类, JOBS 找到 Worker 执行作业 JobPlant JOBS 中的每个作业类型继承 Work MyJobPlant JobPlant 的实例,并实现虚拟工作 Work 抽象类中的方法。

  ... JOBS> 
struct JobPlant:Work< JOBS> ... {
typedef Worker< JobPlant,JOBS ...>工人;
bool worker(const Job& job){return WORKER()(this,job); }
};

struct MyJobPlant:JobPlant< JobA,JobB,JobC> {
void work(const JobA&){std :: cout< 作业A. << std :: endl; }
void work(const JobB&){std :: cout<< 作业B. << std :: endl; }
void work(const JobC&){std :: cout< 作业C. << std :: endl; }
};

int main(){
JobB j;
MyJobPlant()。Worker(j);
}


解决方案

$ c> p> Work< JOB> :: work(),即 Work< JOB> 中的纯虚方法。这个方法没有实现(它毕竟是纯的)。



派生类可能实现/覆盖了该方法并不重要。 Work< JOB> :: 表示您想要该类的版本,而不是派生类的版本。不会发生动态分派。



Work< JOB> :: work()从派生类中的覆盖方法调用基类方法,你真的想要基类方法。)






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



第一步导致歧义:多个基类包含名称 work 。编译器然后不会尝试找出匹配的重载。 (它们不是真的重载,而是彼此冲突,因为它们来自不同的类)。



通常这可以通过将基类方法名称导入类使用(或者技术上称为使用)。如果你为基类的所有工作方法添加使用声明,编译器将找到<$ c $



可变参数模板复杂化因为我不认为使用工作< JOBS> ::工作...; 是合法的(我的编译器不这么认为)。但是如果你手工编写基类,所有的工作方法都可以进入最终类:

  template< typename JOB,typename ... JOBS> 
struct CollectWork:Work< JOB> ;, CollectWork< JOBS ...> {
使用Work< JOB> :: work;
使用CollectWork< JOBS ...> :: work;
};

template< typename JOB>
struct CollectWork< JOB> :Work< JOB> {
使用Work< JOB> :: work;
};

template< typename ... JOBS>
struct JobPlant:CollectWork< JOBS ...> {
使用CollectWork< JOBS ...> :: work;
typedef Worker< JobPlant,JOBS ...>工人;
bool worker(const Job& job){return WORKER()(this,job); }
};

有了这个结构,有问题的 p-> work(dynamic_cast < JOB&>(job)); 已成功编译并运行


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

解决方案

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.

这篇关于从派生可变模板类调用基本模板的虚方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆