Reputation: 1608
Sorry if this question is a bit confusing. I'm really asking for learning purposes and to see if there is a way that this would even be possible.
In the class below, I have to specify a template parameter for CreateObject() and GetObject():
class ObjectManager
{
public:
template <class T>
void CreateObject(int id)
{
//Store pointer to newly created object in a map
}
template <class T>
T* GetObject(int id)
{
//Find object using id and return a T* to it
}
};
CreateObject() uses the template parameter to create the correct type of object and then stores a pointer to it in a map. GetObject() uses the template parameter to return a pointer of the desired type. For some objects, I want GetObject() to return a pointer to the actual object type. For other objects, I want GetObject() to return a pointer to a parent type.
As of right now, any time I call GetObject(), I need to specify the type:
SomeType* pST = m_objectManager.GetObject<SomeType>(id);
AnotherType* pAT = m_objectManager.GetObject<AnotherType>(id);
My goal would be to specify the actual object type and the desired return type as template parameters in my CreateObject() function:
class ObjectManager
{
public:
template <class T, class DesiredReturnType>
void CreateObject(int id)
{
//Store pointer to newly created object of type T in a map
//The DesiredReturnType would be used by GetObject() to return the correct type of pointer
}
//This isn't possible but would be ideal.
DesiredReturnType* GetObject(int id)
{
//Return a DesiredReturnType* to the object
}
};
m_objectManager.CreateObject<SomeType, ParentOfSomeType>(id);
//Now I don't need to specify a template argument when calling GetObject().
//CreateObject() will create a new SomeType.
//GetObject() will return a ParentOfSomeType* which is really pointing to a SomeType object
ParentOfSomeType* pPST = m_objectManager.GetObject(id);
Since every object has a different type and a different desired return type, I wouldn't be able to use class template parameters. The type and desired return return type would always be changing depending on what type of objects I was creating.
Would something like this be possible? Is there some kind of design pattern which would help in a situation like this?
EDIT:
The reason for the design choice is as follows. I will be calling another function which has different behavior depending on whether is receives a Parent* or a Child*.
By taking out the template argument, I thought that I might be able to do something like this:
for(int id = 0; id < 10; id++)
MyFunction(m_objectManager.GetObject(id));
It probably doesn't change the fact that it's a bad decision choice, but I'm mostly asking out of curiosity. :)
Upvotes: 2
Views: 837
Reputation: 9199
Try visitor pattern. Live example at Ideone.com
struct ConcreteVisitor: IVisitor
{
virtual void visit(int)
{
cout << "visiting int" << endl;
}
virtual void visit(double)
{
cout << "visiting double" << endl;
}
virtual void visit(SomeClass&)
{
cout << "visiting SomeClass" << endl;
}
// ..
};
int main(int argc,char *argv[])
{
ObjectManager manager;
ConcreteVisitor visitor;
manager.create_object<int>(1);
manager.create_object<double>(2);
manager.create_object<SomeClass>(3);
manager.apply(1,visitor);
manager.apply(2,visitor);
manager.apply(3,visitor);
return 0;
}
Output is:
SomeClass::SomeClass()
visiting int
visiting double
visiting SomeClass
SomeClass::~SomeClass()
Full code:
#include <boost/shared_ptr.hpp>
#include <boost/ptr_container/ptr_map.hpp>
#include <iostream>
#include <ostream>
#include <map>
using namespace std;
using namespace boost;
typedef int ID;
struct SomeClass
{
SomeClass()
{
cout << "SomeClass::SomeClass()" << endl;
}
~SomeClass()
{
cout << "SomeClass::~SomeClass()" << endl;
}
};
struct IVisitor
{
virtual void visit(int)=0;
virtual void visit(double)=0;
virtual void visit(SomeClass&)=0;
// ..
};
struct ConcreteVisitor: IVisitor
{
virtual void visit(int)
{
cout << "visiting int" << endl;
}
virtual void visit(double)
{
cout << "visiting double" << endl;
}
virtual void visit(SomeClass&)
{
cout << "visiting SomeClass" << endl;
}
// ..
};
struct ITypeStorage
{
virtual void apply_visitor(void *obj,IVisitor &visitor)=0;
};
template<typename ObjectType> struct TypeStorage: ITypeStorage
{
virtual void apply_visitor(void *obj,IVisitor &visitor)
{
visitor.visit( *(static_cast<ObjectType *>(obj)) );
}
};
class ObjectManager
{
map<ID,boost::shared_ptr<void> > objects;
ptr_map<ID,ITypeStorage> types;
public:
template <class T>
void create_object(ID id)
{
objects[id].reset(new T());//shared_ptr will use right deleter
types.insert(id,new TypeStorage<T>());
}
void apply(ID id,IVisitor &visitor)
{
types.find(id)->second->apply_visitor(objects[id].get(),visitor);
}
};
int main(int argc,char *argv[])
{
ObjectManager manager;
ConcreteVisitor visitor;
manager.create_object<int>(1);
manager.create_object<double>(2);
manager.create_object<SomeClass>(3);
manager.apply(1,visitor);
manager.apply(2,visitor);
manager.apply(3,visitor);
return 0;
}
Upvotes: 0
Reputation: 24392
What about class template:
class ObjectManager
{
public:
/// reference to some internal object manager
template <class T, class DesiredReturnType>
class ObjectRef {
public:
ObjectRef(ObjectManager& manager) {}
DesiredReturnType* GetObject()
{
}
};
template <class T, class DesiredReturnType>
ObjectRef<T,DesiredReturnType> CreateObject(int id)
{
}
};
auto ref = m_objectManager.CreateObject<SomeType, ParentOfSomeType>(id);
//Now I don't need to specify a template argument when calling GetObject().
//CreateObject() will create a new SomeType.
//GetObject() will return a ParentOfSomeType* which is really pointing to a SomeType object
ParentOfSomeType* pPST = ref.GetObject(); // no need to specify id...
Upvotes: 0
Reputation: 6447
A function in C++ can only have one return-type and that type must be known at compile-time. A function template can have a return type that's dependent on it's template argument(s). Since C++ is statically typed that dependency must be resolved at compile-time. That means you cannot look up the desired return-type from a map at runtime. You can however derive it from the template argument(s).
EDIT: To clarify: when you "use" a function template like FunctionTemplate<Type>()
or FunctionTemplate(parameterThatsUsedToDeriveTheTemplateArguments)
, the function template is instantiated. That means a normal function with the "name" FunctionTemplate<ARG1, ARG2, ...>
will be created - the so called "specialization" of the template for the arguments ARG1, ARG2 etc. And this function is just like a normal function, which means it also can have only one fixed return type that must be known at compile time. /EDIT
In other words: GetObject
has to have at least one template argument that it uses to derive the return-type. One thing that could work - depending on how you want to use this - would be to encode the desired return-type in the type of the id
parameter.
E.g. something like
template <class T>
struct ObjectId {
typedef T ReturnType;
ObjectId(int id) : m_id(id) {}
int m_id;
};
class ObjectManager {
...
template <class T, class ID> // ID type will be deduced
void CreateObject(ID id) {
...
}
template <class ID> // ID type will be deduced
typename ID::ReturnType* GetObject(ID id) {
...
}
};
...
ObjectManager obman;
auto const idFoo = ObjectId<Foo>(1);
auto const idBar = ObjectId<BarBase>(2);
obman.CreateObject<Foo>(idFoo);
obman.CreateObject<Bar>(idBar);
Foo* foo = obman.GetObject(idFoo);
BarBase* bar = obman.GetObject(idBar);
Upvotes: 1