Reputation: 2877
So I'm trying to create a "Table" class in C++ with a structure like so:
Table.h
class Table
{
private:
class TableImpl;
TableImpl* impl;
};
Table.cpp
class Table::TableImpl
{
private:
class Row
{
private:
template <typename T>
class RowDataCell
{
T data;
}
std::vector<RowDataCell*> data;
};
std::vector<Row*> rows;
};
The TableImpl
consists of a std::vector
of Row
objects, and each Row
object consists of a std::vector
of generic RowDataCell
objects. The only thing is, I can't create the std::vector
because I need to provide a template argument for RowDataCell*
, which will stymie my goals ofhaving a container of miscellaneous objects.
Is there a way using standard C++ that I can accomplish this goal.
Upvotes: 2
Views: 358
Reputation: 23610
There are several ways to do what you want. One way is to define a common interface for the types you want to put into the container and then put pointers to that interface into the vector:
class RowDataCellInterface
{
public:
virtual ~RowDataInterface() {}
virtual std::string toString() const = 0;
virtual std::unique_ptr<RowDataInterface> clone() const = 0;
};
Then you can derive implementations of this interface even in a templated manner:
template <typename T>
class RowDataCell final : public RowDataCellInterface
{
public:
virtual std::string toString() const
{
std::stringstream ss;
ss << data;
return ss.str();
}
virtual std::unique_ptr<RowDataInterface> clone() const
{
std::make_unique<RowDataCell>(*this);
}
private:
T data;
};
Now can put these thingies into a vector like this:
std::vector<std::unique_ptr<RowDataCellInterface>>
and everything should work fine. To copy the vector you should use the clone()
member function of your interface. There are several library solutions out there, which help you accomplish what you want. One of them is boost::variant
which I would suggest. I gave a very detailed answer to a very similar question here. Another library solution is boost::any
, which may look simpler at first, but since in order to do something meaningful with it you have to know the type of the contained thing, which is erased at the moment of access. I would prefer boost::variant
over boost::any
most of the time.
Upvotes: 0
Reputation: 57698
Yes, there is a standard C++ way to implement tables of records and it can get ugly. I tried.
Let's assume that a table is a container of records.
A record can contain fields or records.
Fields can be of different types, such as integer, string and BLOB.
The objective, at least for me, was to keep things as generic as possible until the lowest level, the specialized field. This means that field values are passed by string.
So, here is a simplified model:
struct Component; // The base of everything.
struct Record : public Component {
std::vector<Component *> components;
};
struct Field : public Component {
std::string name;
virtual std::string get_value_as_string(void) = 0;
};
struct Field_String : public Field;
//... And so on.
struct Table {
std::vector<Record *> rows;
};
The fundamental issue is that every cell of the table can potentially be of a different type. The table can have limitless columns and rows.
I also recommend reading about databases. Many database interfaces can resolve the issues of tables.
Upvotes: 0
Reputation: 275395
There are two reasonable approaches.
The first is a discriminating union, the other is a type-safe variant of the old C-style void*
"anything could be here".
I'll first mention two boost
implementations of them:
boost::variant<A,B,C>
(and incoming std::experimental::variant
) is a discriminating union. It can store one thing of type A
, B
or C
. There are various type-safe ways to get the elements out, or perform operations on them via "visiting". Variant has some restrictions on what types it can hold, and more restrictions depending on how you inject those types.
boost::any
(and incoming std::experimental::any
) is a type-safe void*
with value semantics. Almost anything can be stored in it (any
requires your object be CopyConstructable), but you can only access it if you know the exact type of thing stored in it, and ask for it.
Writing either one yourself is doable, but I'd recommend using them, or at the least understanding them and cloning a good part of their interface and patterns.
The variant
can store instances "internally" within itself, and is usually a better approach. You can emulate it with a union
, a list of types, and an index into that list, plus a pile of meta programming boilerplate. Alignment issues are tricky, as an aside.
any
is easier to write, but still a bit of a challenge. It is a really basic type erasure object with only a "cast to type X" (via typeid
or equivalent) and copy exposed. If you have ever seen std::function
be implemented, you are half way there.
Upvotes: 4