Re: Get Derived (templated class) from Base pointer?
Mike Stephenson wrote:
Let's say I have a bunch of objects that are created via a template class:
template<typename TValue, TPolicy1, TPolicy2>
class CDerived
{
TValue _Value;
public:
CDerived(TValue& Val) : _Value(Val) {}
//
TValue& GetValue()
{
return m_Value;
}
//
void DoPolicy1(int i, bool b)
{
TPolicy1::Do(i, b, _Value);
}
//
void DoPolicy2(int i, bool b, _Value)
{
TPolicy2:Do(i, b, _Value);
}
};
Quoting a quote from a recent posting by Jerry Coffin:
"Each name that contains a double underscore (_ _) or begins with an
underscore followed by an upper-case letter (2.11) is reserved to the
implementation for any use." ($17.4.3.1.2).
Now I need to store all of these CDerived objects in a generic container.
Since CDerived is a template class, I can't do this straight away. I have
to create a base class, CBase, that is not templated.
No, you can also use value wrappers like e.g. boost::any or boost::variant<>
if the variance of the types is limited. At least I would use smart
pointers to solve the problem of resource management.
[...]
class CBase
{
virtual void DoPolicy1(int i, bool b) = 0;
virtual void DoPolicy2(int i, bool b) = 0;
};
and we'll change class CDerived to class CDerived : public CBase.
[...]
std::string s("test");
CDerived der<std::string, CMyPolicy1, CMyPolicy2>(s);
std::vector<CBase*> vec;
vec.push_back(&der);
...
// and then later
s = vec[0]->GetValue();
My problem is that vec holds CBase* pointers. CBase does not hold
the Value, it is held in CDerived. GetValue() is in CDerived. It
cannot be in CBase because the return type varies based on TValue
passed into CDerived.
Well, since you already know that the derived class holds a string, you
could use a static_cast to convert from the baseclass to the derived class.
What might be worthwhile is to introduce an intermediate class
template<typename ValueType>
struct ValueHolder: Base
{
ValueHolder( ValueType const& Val):
m_Value(Val)
{}
ValueType& GetValue()
{ return m_Value; }
private:
ValueType m_value;
};
template<typename TValue, TPolicy1, TPolicy2>
struct Derived: ValueHolder<TValue>
{ ... };
which eliminates the policies from the need to be specified in the
static_cast above.
I tried adding an additional virtual function to CBase:
virtual CBase* GetPtr() = 0;
and then creating a typedef in CDerived that is it's current type and
overriding GetPtr():
typedef CDerived<TValue, TPolicy1, TPolicy2> tThis;
tThis* GetPtr()
{
return this;
}
This still does not work.
Using covariant return types...
CBase and CDerive compile fine, because you are allowed to change the
return type in a virtual function if it is compatible with the return
type from the base class, and CDerived<>* is compatible with CBase*
since CDerived is derived from CBase.
What does not work is that GetPtr() in CDerived still returns a CBase
pointer, so I get an error from the compiler that basically "GetValue()
is not a member of CBase".
No, sorry, but this can't work. The point is that if you invoke it on the
baseclass, you still only have the static interface of the baseclass. All
those types are _static_ and determined at compile time. No trick or magic
will raise those barriers.
So, I think that my ultimate problem is that: given only a CBase*, is
there any way I can get a CDerived* that is immediately callable, without
having to case CBase* to a specific CDerived* (of which I don't know the
exact type, because CDerived is a template class).
You want to use the interface of a derived class without wanting to know
which derived class that is. If this would work, how would you or the
compiler know what that function returns and what to store it in? After
all, it could be _any_ instantiation of the template or even a totally
unrelated class. IOW, there is no way except redesigning what you want.
One possible way would be to add GetValue() to the baseclass and return
something like the already mentioned boost::any from it.
Another way would be to specify the type when retrieving the object from the
container. The container would then perform a dynamic_cast and return the
object or throw.
container<base> cont;
cont.insert(new derived1);
derived1* p = cont.get<derived1>(0);
It seems to me that the concepts of "generic programming" (templates) and
containers do not play well at all together when the templated objects
need to be in the containers rather than being the containers.
It's not that, it's totally unrelated to the objects in the container being
template. If they were not templates and derived from a baseclass you would
face the same problems.
I've experimented with "chameloeon objects" such as boost::any and two
others, but the problem with all of them is that while it's easy to add
any arbitrary type of object to the chameleon, if you want to get the
object out you already have to know the type of the object so you can
either cast the chameleon's value or assign it to a variable of the
proper type. My problem is that in trying to make this generic and easy
to use without the user having to write a bunch of handle classes or
something like that, I have to be able to get a pointer out of the
container and call it without having to cast it to a CDerived object
(of which I don't know the exact type).
Well, either you only use the interface of the baseclass (but you can't
define it because parameters differ) or you actually have to know the types
_somewhere_ and then cast accordingly. If the value is the same type in the
whole container, you can wrap the container for the base objects in a "type
firewall" which makes sure inserted objects have the correct type and then
safely (and conveniently) performs the casts to derived class.
It might be possible to give better suggestions if you described a bit how
you expect the user to use your library.
Uli
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]