user396404
user396404

Reputation: 2819

c++ container with with variable size and variable types

I am trying to create something like a list. However, different instances of the list may have a different number of entries, and the type of entry is based on input given by the user. For example, the user states that they want the structure of each entry in the list to contain an int id, a std::string name, a double metricA, and a long metricB. Based on this input, the following is created:

struct some_struct {
    int id;
    std::string name;
    double metricA;
    long metricB;
}

list<some_struct> some_list;

The user input may be read from a file, input on the screen, etc. Additionally, their are a variable number of entries in some_struct. In other words, it may have the entries listed above, it may have just 2 of them, or it may have 10 completely different ones. Is there someway to create a struct like this?

Additionally, being able to apply comparison operators to each member of some_struct is a must. I could use boost::any to store the data, but that creates issues with comparison operators, and also incurs more overhead than is ideal.

Upvotes: 0

Views: 2418

Answers (3)

user396404
user396404

Reputation: 2819

I ended up using a list with a boost::variant. The performance was far better than using boost::any. It went something like this:

#include <boost/variant/variant.hpp>
#include <list>

typedef boost::variant< short, int, long, long long, double, string > flex; 
typedef pair<string, flex> flex_pair;
typedef list< flex_pair > row_entry;

list< row_entry > all_records;

Upvotes: 0

user1342784
user1342784

Reputation:

There are many ways to solve the problem of data structures with varying members and which is best depends a lot on how exactly it is going to be used.

The most obvious is to use inheritance. You derive all your possibilities from a base class:

struct base_struct { 
    int id;
    std::string name;
};

list<base_struct*> some_list;

struct some_struct : public base_struct {
    double metricA;
};


struct some_other_struct : public base_struct {
    int metricB;
};

base_struct *s1 = new some_struct;
s1->id = 1;
// etc

base_struct *s2 = new some__other_struct;
s2->id = 2;
// etc

some_list.push_back(s1);
some_list.push_back(s2);

The tricky bit is that you'll have to make sure that when you get elements back out, you case appropriately. dynamic_cast can do this in a type-safe manner:

some_struct* ss = dynamic_cast<some_struct*>(some_list.front());

You can query the name before casting using type_info:

typeid(*some_list.front()).name();

Note that both these require building with RTTI, which is usually OK, but not always as RTTI has a performance cost and can bloat your memory footprint, especially if templates are used extensively.

In a previous project, we dealt with something similar using boost any. The advantage of any is that it allows you to mix types that aren't derived from one another. In retrospect, I'm not sure I'd do that again because it made the code a bit too apt to fail at runtime because type checking is being deferred until then. (This is true of the dynamic_cast approach as well.

In the bad old C days, we solved this same problem with a union:

struct base_struct {
    int id;
    std::string name;
    union {  // metricA and metricB share memory and only one is ever valid
        double metricA;
        int metricB;
    };
};

Again, you have the problem that you have to deal with ensuring that it is the right type yourself.

In the era before the STL, many container systems were written to take a void*, again requiring the user to know when to cast. In theory, you could still do that by saying list<void*> but you'd have no way to query the type.

Edit: Never, ever use the void* method!

Upvotes: 2

Nikolai Fetissov
Nikolai Fetissov

Reputation: 84239

C++ is a strongly-typed language, meaning you have to declare your data structure types. To that end you cannot declare a struct with arbitrary number or type of members, they have to be known upfront.

Now there are ways, of course, to deal with such issues in C++. To name a few:

  • Use a map (either std::map or std::unordered_map) to create a "table" instead of a structure. Map strings to strings, i.e. names to string representation of the values, and interpret them to your heart.
  • Use pre-canned variant type like boost::any.
  • Use polymorphism - store pointers to base in the list, and have the virtual mechanism dispatch operations invoked on the values.
  • Create a type system for your input language. Then have table of values per type, and point into appropriate table from the list.

There probably as many other ways to do this as there are C++ programmers.

Upvotes: 3

Related Questions