6

I'm designing a simple Array class with the capability of holding any type of object, like a vector that can hold multiple types of data in one object. (This is for learning purposes.)

I have an empty base class called Container:

class Container {};

And a templatized subclass called Object:

template <class T>
class Object : public Container {
    T& object;
public:
    Object(T& obj = nullptr) : object(obj) {}
};

I have an Array class which holds a vector of pointers to Containers which I use to hold Objects:

class Array {
    std::vector<Container *> vec;
public:
    template <class T>
    void add_element(const T&);
    auto get_element(int);
};

add_element stores elements into Objects and puts them into vec:

template <class T>
void Array::add_element(const T& element)
{
    vec.push_back(new Object<T>(element));
}

get_element removes the element from it's Object and passes it back to the caller. This is where my problem lies. In order to remove the element from the Object, I need to know what type of Object it is:

auto Array::get_element(int i)
{
    return (Object</* ??? */> *)vec[i])->object;
}

Is there some way for me to find out what sort of object I'm storing?

Edit: since people are claiming that this is not possible, how about this. Is there some way of actually storing type information inside of a class? (I know you can do that in ruby). If I could do that, I could store the return type of get_element in each Object.

Michael Dorst
  • 8,210
  • 11
  • 44
  • 71
  • @ildjarn: Since the OP is using `auto` in the question, I think it's safe to assume that. – Jesse Good Jul 06 '12 at 22:19
  • @user315052 because It'd be harder to use that way. @ildjarn Yes it does, but if I `decltype(vec[i])` I'll just get a `Container *`. I need to know what subclass it is. – Michael Dorst Jul 06 '12 at 22:19
  • Go have a look at [boost::any](http://stackoverflow.com/questions/4988939/how-do-boostvariant-and-boostany-work) – Martin York Jul 06 '12 at 22:30
  • @MichaelDorst Edit: since people are claiming that this is not possible, how about this. Is there some way of actually storing type information inside of a class? (I know you can do that in ruby). If I could do that, I could store the return type of get_element in each Object. In class Object you can save template parameter by typedef, e.g. typedef T object_type; but it can`t help you. – ForEveR Jul 06 '12 at 22:31
  • @ForEveR how is that storing type information? that will only work once (you can't typedef multiple types to the same identifier e.g. `typedef T obj_type; ... typedef Q obj_type;`) and I need it to work for every element I add to my `Array`. – Michael Dorst Jul 06 '12 at 22:37
  • @ForEveR Also, I don't think you can `typedef` inside of a function. `typedef` is a compile-time thing. – Michael Dorst Jul 06 '12 at 22:44
  • Each instantiation of template class with different type have own fiels, so, for Object object_type will be std::string, for Object object_type will be int etc, also we can declare static variable of such type, but i can`t understood how it can helps you... There is no Reflection in standart C++, so, only RTTI rest. – ForEveR Jul 06 '12 at 22:46
  • @ForEveR So what is the syntax for storing types as variables? It can't be `typedef` can it? I thought `typedef` defined a new type not a variable... – Michael Dorst Jul 06 '12 at 23:00
  • @MichaelDorst: you *can't* store types as variables. – jalf Jul 06 '12 at 23:04
  • May I ask why you are using `nullptr` as the default value of the `obj` parameter in `Object`'s constructor? When you don't provide any argument to the constructor, this is going to fail, isn't it? – mfontanini Jul 07 '12 at 00:48
  • @mfontanini This shouldn't even compile. – Felix Dombek Apr 18 '18 at 22:10

3 Answers3

2

I was thinking something along the lines of this template for your get_element.

template <class T>
T& Array::get_element(int i, T &t)
{
    Object<T> *o = dynamic_cast<Object<T> *>(vec[i]);
    if (o == 0) throw std::invalid_argument;
    return t = o->object;
}

Array arr;
double f;
int c;

arr.get_element(0, f);
arr.get_element(1, c);

But alternatively, you could use this:

template <class T>
T& Array::get_element(int i)
{
    Object<T> *o = dynamic_cast<Object<T> *>(vec[i]);
    if (o == 0) throw std::invalid_argument;
    return o->object;
}

f = arr.get_element<double>(0);
c = arr.get_element<int>(1);
jxh
  • 69,070
  • 8
  • 110
  • 193
  • I don't really like your throw statement, generally I would throw a `std::exception`, but your basic idea is good. No prb - thx =) – Michael Dorst Jul 06 '12 at 22:57
  • 1
    @MichaelDorst: Edit made, regards – jxh Jul 06 '12 at 23:01
  • @MichaelDorst: I saw your edit suggestion. So you are okay with users of your `Array` to call it like `double f = arr.get_element(i)`? Because, I thought `arr.get_element(i, f)` was nicer. – jxh Jul 06 '12 at 23:13
  • well then I would have to make a dummy variable every time I called it. That seems silly. better to just specify the type. – Michael Dorst Jul 06 '12 at 23:15
  • also, I will be overloading `operator []` later, so I'll want the call to be `vec[i];` not `vec[i, f];`. I'm actually not sure if the latter is possible. It's also more clear that I'm casting to `double` if I use it that way. – Michael Dorst Jul 06 '12 at 23:19
  • @MichaelDorst: Gotcha. Happy hacking! – jxh Jul 06 '12 at 23:21
  • @MichaelDorst: when you implement your `operator []`, use the technique described [here](http://stackoverflow.com/questions/9957936/c-array-subscript-operator-template). – jxh Jul 07 '12 at 01:47
1

The return type of a function like Array::get_element must be determined at compile time. Since class Array does not know what types of containers it stores at compile time, the only option is to just return the base Container* pointer. Then users of that code can use virtual functions and/or dynamic_cast to deal with those generalized containers.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • The whole point of this class was so that I wouldn't have to use all that syntax every time I want to access an element. `((Object *)vec[num].->object` is just too messy. – Michael Dorst Jul 06 '12 at 22:23
  • 1
    If `Container` has enough virtual functions, you don't need any casts. – aschepler Jul 06 '12 at 23:28
1

dynamic_cast<>(), when used on a pointer, can be used to check whether the cast is actually possible since it returns NULL when the types don't match.

However, you'll probably want to reconsider your setup; dynamic_cast isn't something you'll want to use if you can avoid it, and having cascades of "if object-is-type1 then do this; else if object-is-type2 then do that; else if object-is-type3..." is almost always a sure sign of bad design.

Using auto in your get_element is probably your trick of getting around the problem that you don't know what the type will be -- that won't work, though. The compiler still has to know the return type; it cannot change at runtime.

EDIT: you might also go a step back and ask yourself whether you REALLY need an array that can store "anything". I've used Objective-C for a long time, and it's basically built around the idea that everything is just an "object" and people can call anything on anything, it's all sorted out at runtime. However, I found that actual uses of this are rare... and for containers in particular, I find that every NSArray that I use only has objects of one type, or maybe of a common base class, but not actually distinct, unrelated classes.

Christian Stieber
  • 9,954
  • 24
  • 23
  • The uses are rare, but it's still necessary to have that capability. My original idea was to make a class that I could use as a wrapper around Obj-C objects (to minimize Obj-C++ code since it's hard to maintain). If I use a vector I can't handle every scenario, which is problematic. – Michael Dorst Jul 06 '12 at 22:31