rcplusplus
rcplusplus

Reputation: 2877

Container of Miscellaneous Types - C++

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

Answers (3)

Ralph Tandetzky
Ralph Tandetzky

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

Thomas Matthews
Thomas Matthews

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

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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

Related Questions