Reputation: 845
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
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
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>;
Upvotes: 1
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