Invalid covariant type with CRTP clonable class
Asked Answered
P

3

8

I'm trying to implement a Clonable class with the CRTP. However, I need to have abstract class that have a pure virtual clone method, overridden by child classes. To make this happen, I need the clone function to return a covariant return type. I made this code below, and the compiler shout at me this error:

main.cpp:12:5: error: return type of virtual function 'clone' is not covariant with the return type of the function it overrides ('B *' is not derived from 'AbstractClonable *')

The class 'B' seems to be a child class of AbstractClonable, and even by two way! How can I solve this? Thank you very much. I tried with both with clang 3.6 and GCC 4.9.2

struct AbstractClonable {
    virtual AbstractClonable* clone() const = 0;
};

template<typename T>
struct Clonable : virtual AbstractClonable {
    T* clone() const override {
        return new T{*dynamic_cast<const T*>(this)};
    }
};

struct A : virtual AbstractClonable {

};

struct B : A, Clonable<B> {

};
Predetermine answered 15/5, 2015 at 5:22 Comment(2)
Do you really need AbstractClonable? Why? I'm really curious. Are there legitimate use cases? You can clone it, and what would you do with the result? Clone it again?Selfoperating
Yes. If I have a collection of A and A is abstract and I need to clone each object, I need something that says that I need to implement clone in subclasses. Since A is abstract, it cannot inherits from Clonable, because of the new in the clone function. I came up with AbstractClonable.Predetermine
G
7

Even if B is indeed derived from Clonable<B>, the problem here is that Clonable<B> construction is not valid, as it defines

B* clone() const override

which of course is not an override of AbstractClonable::clone(), since the compiler doesn't see B at this point as a child of AbstractClonable. So I believe the issue lays in the fact that the compiler cannot build the Clonable<B> base of B.

A workaround (but not really the same as what you want) is to define

Clonable* clone() const override

in Clonable. As you mentioned in the comment, you can also define a free function

template<typename T> 
T* clone(const T* object) 
{ 
    return static_cast<T*>(object->clone()); 
}

Related: Derived curiously recurring templates and covariance

Granadilla answered 15/5, 2015 at 5:47 Comment(8)
shouldn't virtual inheritance take care of that?Snipes
AFAIK, the virtual derivation just removes the "duplicate" base from the picture. I don't think it makes this construction work.Granadilla
B is a child of A and A is a child of AbstractClonable. And we can see that B is a child of the class Clonable<B>, which is a child of AbstractClonable. B is related to AbstractClonable two times. How's that B is not a child of AbstractClonable?Predetermine
B is a child of AbstractClonable, but when first constructing Clonable<B>, B is an incomplete type, and Clonable<B> is not seen as a child of AbstractClonable. I don't think the compiler can successfully fully define B without first trying to resolve its base classes. I have to say I am not 100% sure of this. Related (but without an accepted answer) https://mcmap.net/q/659530/-derived-curiously-recurring-templates-and-covariance/3093378Granadilla
It certainly not the semantic I was looking for, but it will do the trick I guessPredetermine
It did the trick with your workaround and this function below: template<typename T> T* clone(const T* object) { return static_cast<T*>(object->clone()); } Can you add this to your answer? I'll mark it as accepted answer.Predetermine
@GuillaumeRacicot Is the updated edit what you meant? It works for me.Granadilla
I added a new function clone outside the scope of the class to automatically cast down the object. I use it like this: clone(myB)Predetermine
S
6

Yes, B is derived from AbstractClonable, but the compiler doesn't know that during the instantiation of Clonable<B> because B is still incomplete at that point.

C++14 §10.3/8:

If the class type in the covariant return type of D::f differs from that of B::f, the class type in the return type of D::f shall be complete at the point of declaration of D::f or shall be the class type D.

A class has special permission to use itself in a covariant return type. Other classes, including CRTP bases, need to wait until the class is complete before declaring a covariant function.

You can solve the problem using the non-virtual interface idiom (NVI):

class AbstractClonable {
protected:
    virtual AbstractClonable* do_clone() const = 0;
public:
    AbstractClonable *clone() const {
        return do_clone();
    }
};

template<typename T>
class Clonable : public virtual AbstractClonable {
    Clonable* do_clone() const override { // Avoid using T in this declaration.
        return new T{*dynamic_cast<const T*>(this)};
    }
public:
    T *clone() const { // But here, it's OK.
        return static_cast< T * >( do_clone() );
    }
};
Shatterproof answered 15/5, 2015 at 6:13 Comment(0)
E
0

I think the problem is that

T* clone() const override{
    return new T{*dynamic_cast<const T*>(this)};
}

returns B* instead of AbstractClonable *.

Extender answered 15/5, 2015 at 5:41 Comment(2)
Yes it returns a B*, and it's my goal! Shouldn't they be valid covariant types since B inherits from AbstractClonable?Predetermine
Since there is a multiple virtual inheritance involved casting from B* to AbstractClonable* requires additional code to be emitted. This code will perform base class lookup using pointer to a virtual base class. If you call clone() method using a pointer to AbstractClonable compiler probably is not able to perform typecasting properly. So I don't think B* and AbstractClonable* can be considered covariant.Extender

© 2022 - 2024 — McMap. All rights reserved.