12

I haven't done any C++ programming for quite some time and I decided that I would mess around with it a little bit in my spare time so I decided to write me a little database program just for fun and I'm having trouble with creating an array of templated class objects.

What I have is this class which I want to use to represent a field in a database record.

template <class T, int fieldTypeId>
class Field
{
private:
    T field;
    int field_type;
public:
    // ...
};

And I want to use an array of that class to represent a record in a database using this class.

class Database_Record
{
private:
    int id;
    Field record[];
public:
    Database_Record(int);
    Database_Record(int, Field[]);
   ~Database_Record();
};

Where I'm stuck at is the creation of the array in the Database_Record class since that is an array of templated class objects with each element possibly being of a different type and I'm not sure how I need declare the array because of that. Is what I'm trying to do even possible or am I going about it the wrong way? Any help would be greatly appreciated.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
ChadNC
  • 2,528
  • 4
  • 25
  • 39
  • No. All elements of an array are of the same type. Field is a different type than Field or Field. You can't put these in an array together. – Cubic Aug 17 '12 at 16:15
  • Elements of an array have to have the same type. Different instantiations of a class templates are different types. Either the array can only hold one version, or maybe they can all inherit from some base class, and you can have an array of base class pointers? – BoBTFish Aug 17 '12 at 16:15
  • `Field` is not a "templated class". It's *class template*. That distinction is the sole root cause of your confusion. `Field` would be a templated class. – Kerrek SB Aug 17 '12 at 16:28
  • And also, you can always store pointers to your objects in an array of integers. It is the easiest way, I think. Have a good day. – TomeeNS Sep 20 '17 at 15:36

8 Answers8

20

Field<T1> and Field<T2> are two completely different types. To treat them in a vector you need to generialize then somewhere. You may write AbstractField and

struct AbstractField{
  virtual ~AbstractField() = 0;
};

template<class T,int fieldTypeId>
class Field: public AbstractField{
  private:
    T field;
  public:
    const static int field_type;
  public:
    virtual ~Field(){}
};

class Database_Record{
  std::vector<AbstractField*> record; 
  public:
    ~Database_Record(){
      //delete all AbstractFields in vector
    }
};

and then keep a vector of AbstractField. also use vector instead of []. Use AbstractField* instead of AbstractField and write at least one pure virtual in AbstractField.

you may make the destructor of AbstractField pure virtual. and don't forget to delete all AbstractFields. in ~Database_Record()

Neel Basu
  • 12,638
  • 12
  • 82
  • 146
  • 2
    Thanks! Your answer has me going in the right direction now I think. – ChadNC Aug 17 '12 at 19:13
  • @NeelBasu thank you for this answer. It is very instructive for me. One think isn't clear to me. Please can you tell me why it is necessary to have a pure virtual method in the AbstractField? – Steve Feb 07 '21 at 15:51
  • Could anyone please explain to me how you would then read field? You would need to provide a virtual function for it in AbstractField, but then the return type would have to be a template. – help Jan 12 '22 at 20:39
  • @help You may not need to. You can dynamic_cast and call the appropriate getter function of the derived class. – Neel Basu Jan 18 '22 at 20:00
1

You are going the wrong way.

Templates are used to create distinct types: std::vector<int> and std::vector<float> are distinct in much the same way (and as much) as int and float are.

Your syntax is also wrong; to create a dynamic array you'd put the following member in your Database_Record:

 std::vector<Field> record; // if this was possible; however, it's not

To put several objects of distinct type into a single array, they ought to have a common base class.

eq-
  • 9,986
  • 36
  • 38
1

In order to create an array of different types you need a base class for the objects and the array will be an array of pointers to that base class. So, for example,

class Field
{
public:
    virtual ~Field() {}
    virtual std::string toString() const = 0;
    // and possibly other interface functions...
};

template <class T> FieldImpl : public Field
{
public:
    virtual std::string toString() const
    {
        std::stringstream ss;
        ss << val;
        return ss.str();
    }

    // implementation of possibly other interface functions        

private:
    T val;
}

will be the types you need. The array will then be something like

std::vector<std::unique_ptr<Field>> my_array;

You can then do stuff with your array using the interface functions, e. g.

my_array[i]->toString();
Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120
  • Can be std::shared_ptr as well. But it should be some kind of smart pointer for automatic memory management. Otherwise the next memory leak is at the door. – Ralph Tandetzky Aug 17 '12 at 16:30
1

As has been said before, C++ templates don't work like that.

At the same, using inheritance and vectors of pointers is not suitable for implementations of DB records because of performance limitations.

Take a step back and look at the problem in a more abstract way. As I understand from your code, the intent is to package an arbitrary number of fields of different types into a continuous memory block. Schematically:

struct DBRecord {
    Type1 f1;
    Type2 f2;
    Type3 f3;
    Type4 f4;
    // etc...
}

You can achieve this by a bit ugly but practical construct consisting of an abstract template declaration and several specializations.

The declaration would look like this:

template <
    typename T1,
    typename T2 = void,
    typename T3 = void,
    typename T4 = void,
    typename T5 = void,
    typename T6 = void,
    typename T7 = void,
    typename T8 = void,
    typename T9 = void,
    typename T10 = void
> struct DBRecord;

It limits a max number of fields to some specific number obviously. If you need a truly arbitrary number of fields you need to switch to column-oriented paradigm.

Then, partial specializations should declare anatomy of structures for each number of arguments from 1 to 10:

template <
    typename T1
> struct DBRecord <T1, void, void, void, void, void, void, void, void, void> 
{
    int id;
    T1 f1;
    DBRecord(int ID, T1 F1) {/*...*/};
};

template <
    typename T1,
    typename T2
> struct DBRecord <T1, T2, void, void, void, void, void, void, void, void> 
{
    int id;
    T1 f1;
    T2 f2;
    DBRecord(int ID, T1 F1, T2 F2) {/*...*/};
};

// etc...

Now, you can allocate tables as arrays of records of certain types in one new[] call if you want. And, you don't normally care about destruction of each field, since you free memory of the whole structure.

Macros can help to make declaration of such specializations somewhat more compact.

Krit
  • 548
  • 3
  • 9
  • As I said, "If you need a truly arbitrary number of fields, you need to switch to column-oriented paradigm". But then, it will be a completely different problem. My answer illustrates a machine-friendly way of packing fields into structures in C++. – Krit Aug 17 '12 at 19:08
0

You are doing templates wrong. Instantiating class templates with different types will yield two different types again with possibly different sizes, which makes it impossible to store them in an array.

If you want to treat different types uniformly, use inheritance. And when you use inheritance, don't use plain arrays, but vector or std::array.

There is also a bunch of odd things in your code: Why store a fieldTypeId when it is known statically? I guess it is related to the type T you are using as a template parameter. Externalise the mechanism through partial specializations:

template<typename T>
struct fieldTypeId;

template<>
struct fieldTypeId<int> {
  const static int value = 0;
}; 
// etc....

If I'm completely wrong and you really know what you are doing: Use type erasure through some any type (e.g. Boost.Any).

pmr
  • 58,701
  • 10
  • 113
  • 156
0

Consider every instantiation with a different template argument to be a different class. You either need to store a specific class (ie Field<int, 17>) or you need Field to have a non-templated base class which you can store in a list.

David
  • 27,652
  • 18
  • 89
  • 138
0

You can do something like this -

template <class T, int fieldTypeId>
class Field
{
private:
    T field;
    int field_Type;
};

template <class T, int fieldTypeId>
class Database_record
{
private:
    int id;
    std::vector<Field<T, fieldTypeId> > record_;
};
Eternal Learner
  • 3,800
  • 13
  • 48
  • 78
0

Made 2 example classes used for quick debug reports inspired by ToString() override of C#:

class UnknownType_t {
public:
    virtual operator long&() { throw "Unsupported"; };
    virtual operator const std::string() { throw "Unsupported"; };
    virtual void Set(char*, long) = 0;
};

class Number : public UnknownType_t {
public:
    Number(long _n) { n = _n; };
    virtual operator long&() { return n; };
    virtual void Set(char* buf, long size) {
        n = 0;
        memcpy(&n, buf, size);
    }

    long n;
};

class String : public UnknownType_t {
public:
    String(const char *_s) : s(_s) {};
    virtual operator const std::string() { return s; };
    virtual void Set(char* buf, long size) {
        s = std::string(reinterpret_cast<char*>(buf), size);
    }

    std::string s;
};

You can check type trying dynamic_cast, result in common array of UnknownType_t look like {n=123 } or {s="ABC" }.

Base is not pure virtual by intent - required cross getters would have no sense...

Jan
  • 2,178
  • 3
  • 14
  • 26