How can I store objects of differing types in a C++ container?
Asked Answered
S

9

34

Is there a C++ container that I could use or build that can contain, say, int and string and double types? The problem I'm facing is that whenever I try to populate, say, a map, vector or list with, say, the following:

int x;
string y;
double z;

I'm restricted with the format:

list<int> mycountainer;
vector<string> mycontainer;

which forces mycontainer to only consist of one type.

Before anyone suggest generics, that wouldn't work either since the standard vector and list containers that come with C++ are already generic - they can be container for any types but cannot contain multiple types.

I would like to avoid using Boost also if at all possible - I'd prefer it if there is a simple way I could code this myself.

Shebat answered 19/1, 2011 at 17:14 Comment(7)
Er... struct anyone? Or class?Matronly
have you tried to use union?Virgilvirgilia
@Elalfer: In the current standard (this will change with C++0x) you cannot use std::string inside an union.Wall
Why do you want to do that? How do you intend on using the container? How will you verify what you have inserted?Wall
Is this sort of a dup of #251903 ?Quadriga
Checkout Poco::Any ;) Source is easy pocoproject.org/docs/Poco.Any.htmlBucovina
Sorry, I answered this 9 years late!Jemmy
D
27

You could use (or re-implement) boost::any and store instances of boost::any in a container. That would be the safest, since boost::any has probably dealt with much of the edge cases and complexity involved in solving this kind of problem in the general case.

If you want to do something quick and dirty, create a structure or perhaps a union containing members of all potential types along with an enumeration or other indicator of which type is 'active' in the object. Be especially careful with unions as they have some interesting properties (such as invoking undefined behavior if you read the wrong union member, only one of the members can be 'active' at a time, the one that was most recently written to).

I'm curious what you're doing that you need such a construct, though.

Dexter answered 19/1, 2011 at 17:21 Comment(3)
+1 on using boost::any. Also, as I already stated in the comment to @Elalfer, you cannot store std::string in an union.Wall
Er, yup, you're right, oversight on my part. I happened to be glancing through the 0x standard draft at the time (which apparently allow std::strings and such in unions via its 'implicitly deleted function' magic).Dexter
You could use std::any instead if you don't want to use Boost and have access to C++17 :)Ausgleich
O
12

Well, the first question would be: Why do you think you need to store objects of different, totally unrelated types in the same container? That seems fishy to me.

If I had the need, I'd look into boost::variant or boost::any.

Osseous answered 19/1, 2011 at 17:23 Comment(5)
Not fishy at all. I often write programs where I'd like to store overrides in a map keyed on a string or enum ident. I want them in a single structure so I can pass them around altogether, I want them to be any type because the alternative is storing them as strings and parsing them every-time. But yes, boost::any or boost::variant is the way to go.Heartsome
I think a better question is, "what is the purpose of boost::variant" before I dismissed someone quickly. What difference does it make? It's abstract, it's practical, it's whatever you want it to be. Get over it.Dermatophyte
@Dylan: The purpose of boost::variant and boost::any is to store unrelated objects. As such I have used such types, but I doubt it was more than once per decade. Thus my advice to question the design.Osseous
You could have to store different objects in a container, if the container represented an "inventory" for a game, with different "objects" in it...Jemmy
I am not talking of different types, I am talking of unrelated types.Osseous
Q
10

What you want is called a "hetrogenious container". C++ doesn't technically support them in the STL, but Boost does.

Given that, I think you'll find your answer in this question: how-do-you-make-a-heterogeneous-boostmap

Quadriga answered 19/1, 2011 at 17:25 Comment(0)
V
5

You can use either structures, or classes or std::pair.

[edit]

For classes and structs:

struct XYZ {
    int x;
    string y;
    double z;
};
std::vector<XYZ> container;

XYZ el;
el.x = 10;
el.y = "asd";
el.z = 1.123;
container.push_back(el);

For std::pair:

#include <pair>
typedef std::pair<int, std::pair<string, double> > XYZ;
std::vector<XYZ> container;
container.push_back(std::make_pair(10, std::make_pair("asd", 1111.222)));
Vanda answered 19/1, 2011 at 17:17 Comment(3)
Why a pair of a thing and a pair and not a tuple?Walloon
There were no tuples in standard C++ then.Vanda
Those are not elements of different types. Those are elements all of the same type, each containing members of different but constant types.Astra
T
2

You could use a struct that contains all three.

struct Data
{
    int intVal;
    std::string stringVal;
    double doubleVal;
};

Then you could just declare list mycontainer<Data> and use the appropriate value, provided you know what the value type is. If not, add an addition field to the struct that tells you which of the three data types is in use.

struct Data
{
    enum DATATYPE { DT_INT, DT_STRING, DT_DOUBLE } type;

    int intVal;
    std::string stringVal;
    double doubleVal;
};

If you're worried about memory usage, you could probably use a union, though I tend to avoid using them. It might be needless paranoia on my part though.

Thrombocyte answered 19/1, 2011 at 17:19 Comment(2)
Unions are restricted to simple data types, which std::string are not. However there are boost::variant; boost.org/doc/libs/1_45_0/doc/html/variant.htmlBeside
Unions are not restricted to simple data types - you can store a pointer to any object you want inside of a union.Sunshine
S
1

The simplest method is of course to define a struct or class that has members of each of the types you wish to store. Josh's answer suggests Boost.Any, which will hold pretty much anything. If you want to restrict values to only those of types int, double, and std::string, then the better choice would be Boost.Variant.

If you simply don't want to use Boost, then I suggest you get over your hang-ups and use it anyway. "Not Invented Here" is a self-destructive policy. But if you can't use Boost, then you can write your own variant class instead. Andrei Alexandrescu wrote a three-part series on that (part 1, part 2, part 3) a few years ago, and its design inspired the one Boost uses.

Stickup answered 19/1, 2011 at 17:39 Comment(0)
J
1

What I have for this question is not what I hoped would work. By what I think that you would like, is a container that stores multiple value types, that you can access at will.

However, as such, a container would have to specify what value it holds, so you could have a class with 500 data types in it, with a correlating constructor for each data type, however, that would be super memory inefficient.

Here is my proposed suggestion, I have worked on for a day, And I hope it meets your criteria:

#include <iostream>
#include <vector>

using namespace std;

enum class type: unsigned int {int_t, unsigned_int_t, string_t, double_t, float_t, bool_t, unipointer_t, vector_int_t, vector_unipointer_t};//just add item types here and in the switch statement to hold more void_ps in unipointer...

class unipointer {
    void* obj;//the pointer to the data. any type of pointer.
    type objtype;//the object type, kept as an enum class.
    struct void_p {//template magic... ;D
        void* void_ptr;
        template<typename T>//when object is initialized, it converts the the void* pointer to the output value.
        operator T() {
            return reinterpret_cast<T&>(void_ptr);
        }
        void_p(void* val): void_ptr(val) {};
    };
public:
    unipointer(void_p ptr, type ptrtype) : obj(ptr), objtype(ptrtype) {}

    type get_type(void) {//Once you call this function, you know the type of data stored, and can call other functions accordingly.
        return objtype;
    }
    template<typename T>//With a temlate, get any value through a pointer to it.
    T get_ptr(void){
        return reinterpret_cast<T&>(obj);
    }
    template<typename T>//With a temlate, get any value, as an object
    T get_object(void) {
        return *get_ptr<T*>();
    }
    void_p get_auto_pointer(void) {//get any pointer to value, can't be assigned to "auto*"!
        return unipointer::void_p(obj);
    }
    void_p get_auto_object(void) {//get any value, can't be assigned to "auto"!
        return *(void_p*)get_auto_pointer();
    }
};

void process_stuff(unipointer& thing, unsigned int num_of_tabs);

int main() {
    double initialization = 1.2345;
    float even_another = 3.14159f;
    unipointer items(new vector<unipointer>{//one thicc object instance
        //Initialization examles:
        unipointer(new int(-12345), type::int_t),
        unipointer(new unsigned int(4'294'967'295), type::unsigned_int_t),
        unipointer(new string("That is how I store my items."), type::string_t),
        unipointer(&initialization, type::double_t),
        unipointer(&even_another, type::float_t),
        unipointer(new bool(1), type::bool_t),
        unipointer(new unipointer(new unipointer(new unipointer(new string("OMG! NESTING!"), type::string_t), type::unipointer_t), type::unipointer_t), type::unipointer_t),
        unipointer(new vector<int>{ 1,2,3 }, type::vector_int_t),
        unipointer(new vector<unipointer>{
            unipointer(new string("That is how I store my nested items."), type::string_t),
            unipointer(new vector<int>{4,5,6}, type::vector_int_t),
            unipointer(new string("Is your head brimming with ideas yet?"), type::string_t)
        } , type::vector_unipointer_t)
    }, type::vector_unipointer_t);

    cout << "What is in the \"items\" unipointer:" << endl;
    process_stuff(items, 1);
    system("pause");
}

void process_stuff(unipointer& thing, unsigned int num_of_tabs) {
    //declare variables & lamda for interpretaion methods, using variable assignment with "get auto object/pointer"
    unsigned int* test = 0;
    double test_2 = 0;
    auto tab_to_current = [num_of_tabs]() {
        for (unsigned int i = 0; i < num_of_tabs; ++i) {
            cout << "\t";
        }
    };
    //format the thing.
    tab_to_current();
    //look through and do stuff
    switch (thing.get_type()) {//just add item types here and in the enum class to hold more void_ps in unipointer...
    case type::int_t:
        cout << "The integer: " << *thing.get_ptr<int*>() << "." << endl;//one way of getting object back from class
        break;
    case type::string_t:
        cout << "The string: \"" << thing.get_object<string>() << "\"." << endl;//another way
        break;
    case type::unsigned_int_t:
        test = thing.get_auto_pointer();//another way
        cout << "The unsigned integer: " << *test << "." << endl;//don't forget to de-reference it!
        delete test;
        break;
    case type::double_t:
        test_2 = thing.get_auto_object();
        cout << "The double: " << test_2 << "." << endl;//even another way!
        break;
    case type::float_t:
        cout << "The float: " << float(thing.get_auto_object()) << "." << endl;//even another way!
        break;
    case type::bool_t:
        cout << "The boolean: " << *(bool*)thing.get_auto_pointer() << "." << endl;//even another way!
        break;
    case type::unipointer_t:
        cout << "A unipointer, and in it:" << endl;
        process_stuff(*&thing.get_object<unipointer>(), num_of_tabs+1);
        tab_to_current();
        cout << "[End of unipointer]" << endl;
        break;
    case type::vector_int_t:
        cout << "A vector of integers, and in it:" << endl;
        for (unsigned int i = 0; i < thing.get_object<vector<int>>().size(); ++i) {
            tab_to_current();
            cout << "\tItem " << i << ": " << thing.get_object<vector<int>>().at(i) << endl;
        }
        tab_to_current();
        cout << "[End of vector of integers]" << endl;
        break;
    case type::vector_unipointer_t:
        cout << "A vector of unipointers, and in it:" << endl;
        for (unsigned int i = 0; i < thing.get_object<vector<unipointer>>().size(); ++i) {
            process_stuff(*&thing.get_object<vector<unipointer>>().at(i), num_of_tabs + 1);
        }
        tab_to_current();
        cout << "[End of unipointer vector]" << endl;
        break;
    }
}

The "unipointer" class should be initialized with a pointer to any object type, and also the type of object. The class can return, through a function, your data, although it is not very safe, and could be called with the wrong type of data.

This is just an example of what could work, I sure hope that you take inspiration from it.

And, to answer your original question, you would set up a list, or vector with the following format:

vector/list:
|
|unipointer(*double)
|
|unipointer(*int)
|
|unipointer(*string)
|
...
|
end

PS: I am a beginner with objects and templates, so this might be messy. Many apoligies.

Jemmy answered 10/2, 2020 at 16:46 Comment(0)
A
1

In modern C++ we have std::any which is now part of the Standard Library, which makes what you want to do easy.

You use it like this:

  • It's easy to put objects of any type into a std::any
  • Because std::any is a constant type, you can insert it into any STL container (for example, vector, list, map, etc)
  • To get the data back out you have two options...
  • ... the easy option is to do a "try-cast" with std::any_cast and catch std::bad_any_cast exceptions
  • ... There may be a more direct approach using any.type, but the code to do this is more difficult. It seems to me this isn't the intended way
// Pseudocode ...
try
{
    double someDouble = std::any_cast<double>(someAny);
    // converted to double ok, any contained a double, now use it
}
catch(std::bad_any_cast &e)
{
    // could not get a double from the any container, because it does
    // not contain a double...
    // logic to deal with the case goes here ...
}


Alternative you can roll your own solution...

Ane answered 17/4, 2023 at 12:31 Comment(0)
T
0

If you have a finite number of items you need to store, put them in a class or structure.

If there is no limit to the items you would need to store in this container then look at a different way of doing things because the only way of doing it is by storing them as an object, and then casting them to their own type when you need to access them.

However, if any item could potentially be in the container, then you have no way of knowing what type specific items in the container are, and therefore will not be able to cast them.

If C++ contained reflection, there would possibly be a way to do this, but C++ doesn't have reflection.

Thomasson answered 19/1, 2011 at 17:23 Comment(1)
It does have Run Time Type Information (has to be enabled), type_info, typeid, etc. With that and templating you can do some pretty interesting stuff, if you know what you're doing.Kyleekylen

© 2022 - 2024 — McMap. All rights reserved.