Search code examples
c++inheritancecovariance

Change return type of base-class's method in its derived-classes


I know this is not possible if the modified return value is not co-variant with the one in the base class. Still, I'd like to implement something that works that way!

For my specific problem I need some sort of Object called Node with a value that can range from int to double to char or whatever and then I need to build a container (std::vector) of these Nodes that can flexibly accept any type of Node.

I build Node essentially as an empty class (it has something in the real code which is common for all its derived classes but is not useful for this post) and specified two types, NodeInt and NodeDouble that inherit from it.

I then build my class Container with the vector as a member. Now I want to be able to loop through all the elements inside my Container and have their value returned ... how do I do this?

This was my first attempt:

#include <iostream>
#include <vector>
#include <memory>

class Node{

};

class NodeChar : public Node{

    public:
        NodeChar(){ value = 'a'; }
        double getValue(){ return value; }

    private:
        double value;

};

class NodeInt : public Node{

    public:
        NodeInt(){ value = 5; }
        int getValue(){ return value; }

    private:
        int value;

};

class Collection{

  public:
    Collection( std::vector< std::shared_ptr<Node> > vec ){ nodeCollection = vec ; }
    void setNodes( std::vector< std::shared_ptr<Node> > vec ){ nodeCollection = vec ; }
    std::vector< std::shared_ptr<Node> > getNodes(){ return nodeCollection; }
  private:
    std::vector< std::shared_ptr<Node> > nodeCollection;    

};


int main() {

    NodeInt n1;
    NodeChar n2;

    std::vector< std::shared_ptr<Node> > vec (2) ;
    vec[0] = std::make_shared<Node>(n1);
    vec[1] = std::make_shared<Node>(n2);

    Collection C(vec);

    std::cout << (C.getNodes())[0]->getValue() << "  ---  " << 
        (C.getNodes())[1]->getValue() << std::endl;

    return 0;
}

But of course this way gcc complains because ‘class Node’ has no member named ‘getValue’. I need to have at least a virtual method there so that my program knows that each Node will have its own getValue.

As soon as I go and try to specify its value though I need to return something ... and that's a problem!

I solved the problem in two ways, both of which seem unnecessarily complex and ugly ...

ONE

Using a generic pointer void* as a return value and then casting them correctly in a second moment

#include <iostream>
#include <vector>
#include <memory>

class Node{

    public:
        virtual void* getValue() = 0 ;

};

class NodeChar : public Node{

    public:
        NodeChar(){ value = 'a'; }
        NodeChar( char v ){ value = v; }

        void* getValue(){ return &value; }

    private:
        char value;

};

class NodeInt : public Node{

    public:
        NodeInt(){ value = 5; }
        NodeInt( int v ){ value = v; }
        void* getValue(){ return &value; }

    private:
        int value;

};

class Collection{

  public:
    Collection( std::vector< std::shared_ptr<Node> > vec ){ nodeCollection = vec ; }
    void setNodes( std::vector< std::shared_ptr<Node> > vec ){ nodeCollection = vec ; }
    std::vector< std::shared_ptr<Node> > getNodes(){ return nodeCollection; }
  private:
    std::vector< std::shared_ptr<Node> > nodeCollection;    

};


int main() {

    NodeInt n1;
    NodeChar n2;

    std::vector< std::shared_ptr<Node> > vec (2) ;
    vec[0] = std::make_shared<NodeInt>(n1);
    vec[1] = std::make_shared<NodeChar>(n2);

    Collection C(vec);

    int* jnk1 = static_cast<int*>((C.getNodes())[0]->getValue());
    char* jnk2 = static_cast<char*>((C.getNodes())[1]->getValue());

    std::cout << *jnk1 << "  ---  " << *jnk2 << std::endl;

    // can't seem to make it work with shared pointers rather than standard int* or double* for the conversion from void*

    return 0;
}

TWO

Essentially the same but returning void (so nothing) but requiring as an argument a generic pointer void* to be updated in the method. This needs again casting it back ...

#include <iostream>
#include <vector>
#include <memory>

class Node{

    public:
        virtual void getValue( void *& ptr ){}

};

class NodeChar : public Node{

    public:
        NodeChar(){ value = 'a'; }
        NodeChar( double v ){ value = v; }

        void getValue( void *& ptr ){ ptr = &value; }

    private:
        char value;

};

class NodeInt : public Node{

    public:
        NodeInt(){ value = 5; }
        NodeInt( int v ){ value = v; }
        void getValue( void *& ptr ){ ptr = &value; }

    private:
        int value;

};

class Collection{

  public:
    Collection( std::vector< std::shared_ptr<Node> > vec ){ nodeCollection = vec ; }
    void setNodes( std::vector< std::shared_ptr<Node> > vec ){ nodeCollection = vec ; }
    std::vector< std::shared_ptr<Node> > getNodes(){ return nodeCollection; }
  private:
    std::vector< std::shared_ptr<Node> > nodeCollection;    

};


int main() {

    NodeInt n1;
    NodeChar n2;

    std::vector< std::shared_ptr<Node> > vec (2) ;
    vec[0] = std::make_shared<NodeInt>(n1);
    vec[1] = std::make_shared<NodeChar>(n2);

    Collection C(vec);

    void* jnk1 ; (C.getNodes())[0]->getValue(jnk1); 
    int* n1ptr = static_cast<int*>(jnk1);
    void* jnk2 ; (C.getNodes())[1]->getValue(jnk2); 
    char* n2ptr = static_cast<char*>(jnk2);

    std::cout << *(n1ptr) << "  ---  " << *(n2ptr) << std::endl;

    return 0;
}

Is(are?) this the only way to make it work? Is there any better solution you can think of or any other way to implement the same thing (i.e. a virtual method in the base class that has different non-covariant return values depending on the derived classes)?

EDIT: As mentioned in the comment another way could be using templates, but if I template Node then when I go to build a container I need to have only Node and I cannot use a mix of different specialised classes, hence (if I'm not missing something) this won't solve the problem for me!


Solution

  • You could also use boost::any, a type safe generic wrapper around any type. When accessing the value you do have to know (or be able to guess) what kind of value you have but it allows you to return any kind of value.