Josh C.
Josh C.

Reputation: 387

C++ Returning Multiple Types as Reference

Ok so I'm trying to setup a template method that returns a reference of an undetermined type based on a parameter request. Everything looks fine but it keeps telling me that no overloaded method of the provided template method exists when I call it. The code looks something like this:

class IObj {
    public:
    int id;
}
class ObjOne : public IObj {}
class ObjTwo : public IObj {}
class ObjThree : public IObj {}

enum ObjectTypes {
    O1Type,
    O2Type,
    O3Type
}

class ObjManager {
    public:
    std::vector< std::unique_ptr<ObjOne> > O1Holder;
    std::vector< std::unique_ptr<ObjTwo> > O2Holder;
    std::vector< std::unique_ptr<ObjThree> > O3Holder;

    ObjManager() {}

    template <class T>
    T& GetObject(int oID, ObjectTypes oType) {
        if(oType == ObjectTypes::O1Type) {
            for(int i = 0; i < O1Holder.size(); i++) {
                if(O1Holder[i]->id == oID) {
                    return *O1Holder[i];
                }
            }
        }
        else if(oType == ObjectTypes::O2Type) {
            for(int i = 0; i < O2Holder.size(); i++) {
                if(O2Holder[i]->id == oID) {
                    return *O2Holder[i];
                }
            }
        }
        else if(oType == ObjectTypes::O3Type) {
            for(int i = 0; i < O3Holder.size(); i++) {
                if(O3Holder[i]->id == oID) {
                    return *O3Holder[i];
                }
            }
        }
    }
}

int main() {
    std::unique_ptr<ObjManager> oManager(new ObjManager());

    ObjOne& a = oManager->GetObject(0, ObjectTypes::O1Type);
    return 0;
}

Everything works fine, and I can make a method that returns a reference to the object stored in the vectors if I return their specific type, but I'm trying to reduce the redundancy of making many functions to return each different type. So I wanted to make a templated method that would return an object type based on which ever type I requested.

It's not giving me any errors it just keeps underlining the -> in the expression oManager->GetObject, and tells me there is no overloaded method for the template method call. Specifically it states "no instance of function template 'ObjManager::GetObject' matches the argument list, argument types are (int, ObjectTypes)" even though I'm passing an integer and ObjectTypes:: into the function's parameter list. I've looked all over for an answer to this but have not been able to find a similar situation to draw experience on.

EDIT: Sorry should have specified that this is a precursor to a vast list of vectors, I just put 3 of them for simplicity. That's why I'm trying to make a single function that can handle the return of different types so that I don't have to make a return function for every vector I create. And the purpose of returning a reference to the specified type is because each derived type will have unique data that is not in the base class, so I'm pulling the objects for editing.

Upvotes: 2

Views: 184

Answers (5)

Josh C.
Josh C.

Reputation: 387

Ok so after much research, I have determined the best way to accomplish this is to create a custom container class like so:

#include <vector>
#include <memory>

class Base {
    public:
    int ID;

    Base(int id) { ID = id; }
}

class A : public Base {
    public:
    int X;

    A(int id) : Base(id) {}
}

class B : public Base {
    public:
    int Y;

    B(int id) : Base(id) {}
}

template <class T>
class MyContainer {
    private:
    std::vector<std::unique_ptr<T>> internalContainer;

    public:
    MyContainer() {}
    ~MyContainer() {}

    void CreateItem(int id) {
        std::unique_ptr<T> newItem(new T(id));
        internalContainer.push_back(std::move(newItem));
    }

    T& GetItem(int id) {
        for(std::vector<std::unique_ptr<T>>::iterator it = internalContainer.begin(); it!= internalContainer.end(); ++it) {
            if((*it)->ID == id) {
                return **it;
            }
        }
    }
}

int main() {
    MyContainer<A> AList;
    MyContainer<B> BList;

    AList.CreateItem(0);
    BList.CreateItem(0);

    A& AOne = AList.GetItem(0);
    B& BOne = BList.GetItem(0);

    AOne.X = 10;
    BOne.Y = 20;

    std::cout << std::to_string(AOne.X) << "\n";
    std::cout << std::to_string(BOne.Y) << "\n";
}

Let me know your opinions on if this is acceptable or if it can be improved! :)

Upvotes: 0

Christophe
Christophe

Reputation: 73542

Root cause

Template argument type deduction can't be solely based on the return type of the function.

On the way to a solution

You could therefore add a dummy function argument to transfer the type information:

template <class T>
T& GetObject(int oID, ObjectTypes oType, T&x) {
    ...
} 

and in main():

ObjOne& a = oManager->GetObject(0, ObjectTypes::O1Type, a);

Then the template type can be deduced.

But this will not solve your problem. This type deduction is at compile time, so that all the possible returns of the function should return the same type (or something that can be converted to it).

This is not the case of your code, which will lead to other compilation errors (see online failure).

The solution

The only workable solution is that you determine the common denominator to return. Make the function a non-template function returning an IObj&:

IObj& GetObject(int oID, ObjectTypes oType) {
...
} 

You should then manage the return object as a polymorphic obhect as well. As the return is by reference, this is fine (i.e. no slicing occurs). The returned reference will really refer to the object returned, whatever its derived type could be. But you'd have to redesign your calling code for polymorphism:

IObj& a = oManager->GetObject(0, ObjectTypes::O1Type);

Online demo

But this is somewhat clumsy because you indicate in an enum the expected type, but then end with a reference to a parent that you can't handle so easily.

Conclusion

As you indicate in the function the expected return type, you'd better go for the solution in Yussuf's rexcellent answer, but applying the technique of the dummy argument for type deduction.

Upvotes: 0

Peter A
Peter A

Reputation: 21

You seem to be trying to use both run-time polymorphism AND the compile-time (template) polymorphism. It doesn't work this way. You cannot return multiple types from the SAME METHOD. What you probably want to do is to either define a method like @yussuf described, or to fully start using run-time polymorphism - in which case you don't need three containers, and the type becomes part of the object ID. I concur with @yussuf's approach. Just do that, it probably will solve your problem. I would also recommend to use a hash / map instead of performing linear search, but this is a different story...

Upvotes: 0

user3684240
user3684240

Reputation: 1590

It is not possible to change the return type of a function based on runtime information (such as your parameters), because they are obviously unknown to the compiler.

If you will always know at compile time which object type you are going to choose, you can use a trick:

Step 1: Turn your enum into a couple of empty structs:

struct O1Type {};
struct O2Type {};
struct O3Type {};

Step 2: Instead of using else ifs, use function overloading:

ObjOne& GetObject(int oID, O1Type) {/* TODO*/}
ObjTwo& GetObject(int oID, O2Type) {/* TODO*/}
ObjThree& GetObject(int oID, O3Type) {/* TODO*/}

You can now use

ObjOne& a = oManager->GetObject(0, O1Type());

(or, even better auto& a = oManager->GetObject(0, O1Type());)

Upvotes: 0

yussuf
yussuf

Reputation: 635

As @tobi303 commented, you should definetly use the template Parameter T in your GetObject class. Then you would actually avoid repeating yourself as the Compiler will generate the code for you that you have repeated 3 times

template <class T>
    T& GetObject(int oID) {
            for(int i = 0; i < OHolder<T>.size(); i++) {
                if(OHolder<T>[i]->id == oID) {
                    return *OHolder<T>[i];
                }
        }

While you would have to define a OHolder Template function, too.

Upvotes: 2

Related Questions