Owen Qian Owen Qian - 3 months ago 23
C++ Question

How to implement a function with a covariant return type of another derived class?

My program has an abstract class, BaseNode, with 2 derived classes, ChoiceNode and OpponentNode. I want to write a pure virtual function in the BaseNode called "returnOpposite" which should return an OpponentNode if called from a ChoiceNode, and a ChoiceNode if called from an OpponentNode.

In BaseNode.h

class BaseNode {
protected:
virtual BaseNode& returnOpposite() = 0;
}


In ChoiceNode.h

#include "BaseNode.h"

class ChoiceNode: public BaseNode {
OpponentNode& returnOpposite();
}


In OpponentNode.h

#include "BaseNode.h"

class OpponentNode: public BaseNode {
ChoiceNode& returnOpposite();
}


My problem here is that Opponent/ChoiceNode needs to have knowledge of the opposite class, which would normally be solved with the use of a forward declaration, BUT in order for the compiler to recognize that the opposite class is covariant with BaseNode, it needs contextual information about the class.

It is my understanding that this information is provided by including the appropriate header file. However, doing so results in something of a circular dependency. ChoiceNode would need to include OpponentNode, which itself needs to include ChoiceNode, but with header guards in place, it seems that OpponentNode will not know of the ChoiceNode class declaration.

How do I solve this apparent catch-22? Is there a way to provide the contextual information about a class without getting involved in a circular dependency?

Answer

Due to the circular dependency issue you have identified, you cannot use a true co-variant return type, as you've discovered.

However, you can use a technique that worked prior to the introduction of covariant return types.

First, I would recommend changing the declaration in all classes to virtual Node& returnOppositeImpl(), making the base class pure virtual. I would also, depending on the remainder of your class hierarchy, declare the function and all implementations as private. You would then declare a non-virtual protected (or public) method in each class returnOpposite, and using the signatures you described above. These function definitions, because they shadow each other, do not require co-variant return types, and their implementations can be placed in their respective .cpp files. The implementation is a simple static or dynamic cast, invoking the returnOppositeImpl method.

Using shadowing, you'll always invoke the returnOpposite that corresponds to the static type at the invocation site, so it will have the correct reference type. But the actual knowledge of the linkage between the various Node classes is hidden the the implementation, breaking the dependency loop.

So, taken all together

In BaseNode.h

class BaseNode {
    private:
        virtual Node& returnOppositeImpl() = 0;
    protected: // Or public:
         Node& returnOpposite() {
            return returnOppositeImpl();
         }
}

In ChoiceNode.h

#include "BaseNode.h"
class OpponentNode;
class ChoiceNode: public BaseNode {
    private: 
        virtual Node& returnOppositeImpl();
    protected: // or public:
        OpponentNode& returnOpposite();
}

In ChoiseNode.cpp

#include "ChoiseNode.h"
#include "OpponentNode.h"
OpponentNode& ChoiseNode::returnOpposite()
{
  // You can use static cast here, if Node doesn't have virtual
  // members, and/or if you can guarantee further subclasses
  // will properly always return an OpponentNode reference.

  return dynamic_cast<OpponenentNode&>(returnOppositeImpl());
}

OpponentNode.h and OpponentNode.cpp are similar to the ChoiseNode implementations

Comments