Overriding a virtual function with a covariant return type in a template derived class

╄→尐↘猪︶ㄣ 提交于 2020-08-08 07:03:12

问题


I would like to override a virtual function in a template-derived class. However, I would like to use the derived class as return type. Here is the corresponding code:

class Abstract {
  public:
    virtual Abstract* allocate() const = 0;
};
template <typename Derived>
class Base : public Abstract {
  public:
    Derived* allocate() const override {
      return new Derived;
    }
};
class Concrete : public Base<Concrete> {
  public:
};
int main() {
  Concrete c;
  delete c.allocate();
}

Unfortunately, my compiler does not recognize that Derived is actually derived from Abstract and fails with the following error message.

mwe.cpp: In instantiation of ‘class Base<Concrete>’:
mwe.cpp:12:25:   required from here
mwe.cpp:8:14: error: invalid covariant return type for ‘Derived* Base<Derived>::allocate() const [with Derived = Concrete]’
     Derived* allocate() const override {
              ^~~~~~~~
mwe.cpp:3:23: note: overridden function is ‘virtual Abstract* Abstract::allocate() const’
 virtual Abstract* allocate() const = 0;
                       ^~~~~~~~

Moving the allocate function into the Concrete class would solve the issue, but leads to code duplication, when creating multiple concrete classes. Is there a way to make the compiler aware that Derived is actually derived from Abstract?


回答1:


You can simulate a covariant return type:

The point here is the class Reintroduce. Without it allocate would override the virtual function. With it, it hides the inherited function.

#include <iostream>

class Abstract {
  public:
    virtual Abstract* allocate() const = 0;
};

namespace details{
    class Reintroduce {};

    template <typename Derived>
    class AllocImpl : public Abstract
    {
    public:
        Abstract* allocate() const override {
            return new Derived;
        }
    };
}


template <typename Derived>
class Base : public details::AllocImpl<Derived> 
{
  public:
    Derived* allocate(details::Reintroduce = {}) const 
    {
      return static_cast<Derived*>(details::AllocImpl<Derived>::allocate());
    }
};

class Concrete : public Base<Concrete> {
  public:
};


int main() {
  Concrete c;
  delete c.allocate();
}

It would be great if we could implement it without AllocImpl but that would make the call ambiguous.




回答2:


Issue is that Concrete is not complete in Base<T> (neither Base<T>) (as for any CRTP).

So compiler don't know that Concrete inherits from Abstract yet.

Possible solution is to not use CRTP but regular inheritance:

template <typename Base>
class Derived : public Base {
  public:
    Derived* allocate() const override {
      return new Derived;
    }
};
class ConcreteImpl : public Abstract {
  public:
};

using Concrete = Derived<ConcreteImpl>;

Demo




回答3:


Modifying Bernds idea of introducing a type tag to distinguish the different allocate functions, we can create a simpler solution. The trick is, that we introduce the type tag in the Abstract class and that we override the allocate method without providing a default parameter (to avoid ambiguity). The drawback of the following solution is that we need to be able to change the signature of the allocate method in the Abstract class, which might not always be possible.

class BaseTag {};
class Abstract {
  public:
    virtual Abstract* allocate(BaseTag = {}) const = 0;
};

template <typename Derived>
class Base : public Abstract {
  public:
    Abstract* allocate(BaseTag) const override { return allocate(); }
    Derived* allocate() const {
      return new Derived;
    }
};

class Concrete : public Base<Concrete> {
  public:
};

int main() {
  Concrete c;
  delete c.allocate();

  Abstract* a = &c;
  delete a->allocate();
}


来源:https://stackoverflow.com/questions/62919503/overriding-a-virtual-function-with-a-covariant-return-type-in-a-template-derived

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!