H. Rittich
H. Rittich

Reputation: 845

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

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?

Upvotes: 3

Views: 341

Answers (3)

H. Rittich
H. Rittich

Reputation: 845

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

Upvotes: 2

Jarod42
Jarod42

Reputation: 217085

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

Upvotes: 1

Bernd
Bernd

Reputation: 2221

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.

Upvotes: 1

Related Questions