views:

586

answers:

3

I have a quite complex class hierarchy in which the classes are cross-like depending on each other: There are two abstract classes A and C containing a method that returns an instance of C and A, respectively. In their inherited classes I want to use a co-variant type, which is in this case a problem since I don't know a way to forward-declare the inheritance relation ship.

I obtain a "test.cpp:22: error: invalid covariant return type for ‘virtual D* B::outC()’"-error since the compiler does not know that D is a subclass of C.

class C;

class A {
public:
        virtual C* outC() = 0;
};

class C {
public:
        virtual A* outA() = 0;
};


class D;

class B : public A {
public:
        D* outC();
};

class D : public C {
public:
        B* outA();
};

D* B::outC() {
        return new D();
}

B* D::outA() {
        return new B();
}

If I change the return type of B::outC() to C* the example compiles. Is there any way to keep B* and D* as return types in the inherited classes (it would be intuitive to me that there is a way)?

A: 

You can't do this due to client side expectation. When using a C instance, you can't tell which kind of C it is (a D or something else). Thus, if you store the B pointer (resulting from a call to the derived class but you don't know it at compile time) into a A pointer, I'm not sure that all the memory stuff will be right.

When you call a method on a polymorphic type, the runtime environment has to check the dynamic type of the object and it moves pointers to suit to your class hierarchy. I'm not sure that you should rely on covariance. Have a look at this

Seb
You've misunderstood. Covariant return types are *allowed*. If the client expects an `A` pointer, but you give it a `B` pointer, that's find because all instances of `B` *are* instances of `A` as well. The problem in this case is that the compiler cannot verify at the declaration of `B::outC` that the new return type `D` is a descendant of the original return type `C`. If the compiler could have seen the full definition of `D`, it would have allowed the new signature. Note that the compiler issues no complaints about `D::outA` because it knows `C` is a descendant of `A`.
Rob Kennedy
In my example, the co-variant return type in class D is not a problem.
Searles
+4  A: 

I know of no way of having directly coupled covariant members in C++. You'll have either to add a layer, or implement covariant return yourself.

For the first option

class C;

class A {
public:
        virtual C* outC() = 0;
};

class C {
public:
        virtual A* outA() = 0;
};


class BI : public A {
public:
};

class D : public C {
public:
        BI* outA();
};

class B: public BI {
public:
        D* outC();
};

D* B::outC() {
        return new D();
}

BI* D::outA() {
        return new B();
}

and for the second

class C;

class A {
public:
        C* outC() { return do_outC(); }
        virtual C* do_outC() = 0;
};

class C {
public:
        virtual A* outA() = 0;
};


class D;

class B : public A {
public:
        D* outC();
        virtual C* do_outC();
};

class D : public C {
public:
        B* outA();
};

D* B::outC() {
        return static_cast<D*>(do_outC());
}

C* B::do_outC() {
        return new D();
}

B* D::outA() {
        return new B();
}

Note that this second option is what is done implicitly by the compiler (with some static checks that the static_cast is valid).

AProgrammer
+1  A: 

As far as I know, there's no way to do this without explicit casting. The problem is that the definition of class B can't know that D is a subclass of C until it sees a full definition of class D, but the definition of class D can't know that B is a subclass of A until it sees a full definition of class B, and so you have a circular dependency. This can't be resolved with forward-declarations because a forward declaration unfortunately cannot specify an inheritance relationship.

There's a similar problem with trying to implement a covariant clone() method using templates, which I found can be solved, but the analogous solution still fails here because the circular reference remains impossible to resolve.

Tyler McHenry
This is indeed a problem of circular dependency. The same applies when you try to use cross-dependent typedefs, enums, fields etc. +1
doc
Everyone has been bitten by this `clone` problem... What I don't like about the solution presented it the wrapping part tough, `typedef` cannot be forward declared and it's a pain if your clients have to know not to use the undecorated name :/
Matthieu M.