27

I saw this nice graphic which classifies which STL container would suit based on different requirements of data such as:

-- Fixed Size Vs Variable size

-- Data of same tyme Vs different type

-- Sorted Vs unsorted data

-- Sequential Vs random access

http://plasmahh.projectiwear.org/cce_clean.svg

I notice in that image, that C++ STL there is no container which is

  1. Variable Size
  2. Heterogenous (data of different types).

Doesn't C++ have something for this?

PS - There can be many permutations made out the different properties of the containers and many others too might not be provided in STL.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
goldenmean
  • 18,376
  • 54
  • 154
  • 211
  • From the image I could make out that many containers like `vector`, `queue`, `stack` etc. are shown as **variable size** based on their colors. Not sure though. – iammilind Oct 18 '11 at 09:23
  • you can use a vector of pointer to abstract base class, even if other solutions are probably better – Ruggero Turra Oct 18 '11 at 09:28
  • 1
    This graphic isn't about the STL, it is about the C++11 standard library. – PlasmaHH Oct 18 '11 at 09:47

8 Answers8

22

Well generally C++ Containers are designed to hold objects of a single type using templates. If you want different types that are all derived from one type you can store a container of pointers (I guess you could also have a container of void* to anything...) e.g. std::vector<MyBaseType*>.

If you want completely unrelated types, you can store objects that can safely reference those other types, such as boost::any.

http://www.boost.org/doc/libs/1_47_0/doc/html/any.html

Some examples off the boost site:

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

using boost::any_cast;
typedef std::list<boost::any> many;

void append_int(many & values, int value)
{
    boost::any to_append = value;
    values.push_back(to_append);
}

void append_string(many & values, const std::string & value)
{
    values.push_back(value);
}

bool is_int(const boost::any & operand)
{
    return operand.type() == typeid(int);
}
bool is_char_ptr(const boost::any & operand)
{
    try
    {
        any_cast<const char *>(operand);
        return true;
    }
    catch(const boost::bad_any_cast &)
    {
        return false;
    }
}

boost::variant is similar, but you specify all the allowed types, rather than allowing any type in your container.

http://www.boost.org/doc/libs/1_47_0/doc/html/variant.html

std::vector< boost::variant<unsigned, std::string> > vec;
vec.push_back( 44);
vec.push_back( "str" );
vec.push_back( SomthingElse(55, 65) ); //not allowed
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Fire Lancer
  • 29,364
  • 31
  • 116
  • 182
10

The basic principle in the standard library is that "containers" are homogeneous; the C++ standard doesn't consider things like std::pair or std::tuple to be containers. (I'd consider the graph misleading, since it does consider them as containers.) If you need a heterogeneous container, you'd have to use a container of boost::variant, or something along those lines.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
5

A library which is not yet accepted into Boost. But which was proposed for inclusion is targeted toward this :

http://rawgit.com/joaquintides/poly_collection/website/doc/html/index.html

It provides a nice class named any_collection which allows one to have an heterogeneous container via boost::type_erasure::any : http://rawgit.com/joaquintides/poly_collection/website/doc/html/poly_collection/tutorial.html#poly_collection.tutorial.basics.boost_any_collection

Otherwise in C++17 there are simple way to implement this : https://gieseanw.wordpress.com/2017/05/03/a-true-heterogeneous-container-in-c/

Quoting the example of the aforementioned article :

namespace andyg{
struct heterogeneous_container{
private:
    template<class T>
    static std::unordered_map<const heterogeneous_container*, std::vector<T>> items;
public:
    template <class T>
    void push_back(const T& _t)
    {
        items<T>[this].push_back(_t);
    }
};

// storage for our static members
template<class T>
std::unordered_map<const heterogeneous_container*, std::vector<T>> heterogeneous_container::items;
} // andyg namespace

Then usable easily :

andyg::heterogeneous_container c;
c.push_back(1);
c.push_back(2.f);
c.push_back('c');
struct LocalStruct{};
c.push_back(LocalStruct{});

The author states it's a toy implementation, but I think this is a really clever way to implement it, and has a simplicity advantage over poly_collection or a vector of variants.

daminetreg
  • 9,724
  • 1
  • 23
  • 15
  • `poly_collection` [has been accepted](https://lists.boost.org/boost-announce/2017/05/0505.php) to boost 10 days after you wrote this comment. – Ilya Popov Jul 07 '17 at 23:12
  • I don't understand, can't the static variable only have a template paremeter incerted once, or does it have multiple template instantiations? Can someone point me to somewhere I could understand this sort of thing better? – The Floating Brain Nov 14 '17 at 17:56
4

std::pair and std::tuple are hardly C++ containers.... so no, there is no heterogeneous containers in the STL, because it's not necessary to have them built-in.

There are several approaches to create such containers. The approaches I would recommend are:

  • using polymorphism
  • using a variant type

For Polymorphism, you can check Boost Pointer Container library.

boost::ptr_vector<Base> vec;
vec.push_back(new Derived);
vec.push_back(new Derived2);

It mimicks the STL containers, but provides functionalities geared toward polymorphism:

  • Access elements as Base&
  • Automatic memory handling
  • Specific copy behavior (using new_clone methods)
  • Syntactic sugar: given boost::ptr_vector<Base>::iterator it;, *it is a Base&

If your types are unrelated, the other possibility is to use Boost Variant. Basically, a variant is similar to:

enum { Type1, Type2, ... } _type;
union {
  SomeType1 _1;
  SomeType2 _2;
  ...
} _u;

Of course, since it's boost, it provides specific guarantees to make sure that you can only access the member of the union that is currently active and lifts the restriction on classes with constructors / destructors not being usable in traditional unions.

It also provides facilities, like the static_visitor, which is the equivalent of a switch on the type, and will make the compilation error out if one of the possible states is not visited.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
2

The fixed size heterogenous containers (like std::tuple require the types to be known at compile time. If you want to make a variable sized heterogeneous container, just make a std::vector<std::tuple<T1,T2,...,TN>>.

If you want a heterogeneous container where the types is not known at compile time (whether that would be variable or fixed sized) you'll have to store pointers (or smart pointers) to a base type known at compile time, or alternatively consider something like a container of boost::any. The STL doesn't directly provide such a container in either fixed or variable sized with heterogeneous elements determined at run time.

Clinton
  • 22,361
  • 15
  • 67
  • 163
  • 3
    A `std::tuple` and `std::pair` is a heterogeneous container as much as any given UDT. For instance `struct foo { int i; float f; std::string s; };`, a heterogeneous container. – dalle Oct 18 '11 at 09:30
  • +1, the STL is all about composition. There's no need to have all possible combinations pre-defined. – MSalters Oct 18 '11 at 09:34
  • @dalle: Not really, you can [iterate](http://stackoverflow.com/questions/3316573/c0x-iterating-through-a-tuple-with-a-function) through the members of a tuple. – MSalters Oct 18 '11 at 09:35
  • I'm not sure how these are comments on my answer. I didn't say the std doesn't have heterogeneous containers, I just said it doesn't have containers which have an unknown type at compile time. – Clinton Oct 18 '11 at 23:59
1

I would point you to this library. It is implemented to be true heterogeneous container https://github.com/hosseinmoein/DataFrame It does not use polymorphism and consequently store pointers. It uses continuous memory storage, just like a std::vector.

You can write code like this

typedef StdDataFrame<unsigned long> MyDataFrame;

MyDataFrame                df;
std::vector<int>           intvec = { 1, 2, 3, 4, 5 };
std::vector<double>        dblvec = { 1.2345, 2.2345, 3.2345, 4.2345, 5.2345 };
std::vector<double>        dblvec2 = { 0.998, 0.3456, 0.056, 0.15678, 0.00345,
                                       0.923, 0.06743, 0.1 };
std::vector<std::string>   strvec = { "Insight", "John Dow", "Alakazam",
                                      "Persian Prince", "Bugs Bunny" };
std::vector<unsigned long> ulgvec = { 1UL, 2UL, 3UL, 4UL, 5UL, 8UL, 7UL, 6UL }
std::vector<unsigned long> xulgvec = ulgvec;

// This is only one way of loading data into a DataFrame instance. There are
// many different ways of doing it. Please see the documentation,
// or dataframe_tester.cc
int rc = df.load_data(std::move(ulgvec),  // Index column
                      std::make_pair("int_col", intvec),
                      std::make_pair("dbl_col", dblvec),
                      std::make_pair("dbl_col_2", dblvec2),
                      std::make_pair("str_col", strvec),
                      std::make_pair("ul_col", xulgvec));
hmoein
  • 11
  • 2
  • 1
    You should generally avoid posting link-only answers. Consider adding some snippets of code along with explanations. If you wish to just share the link, it should be added as a comment on the original question instead of as an answer. – sshashank124 Jan 02 '20 at 16:32
1

Noted all the answers mentioning about boost::variant.

But from C++17 the standard itself provides std::variant. It is a type-safe union.

    //A vector holds multiple types as declared in std::variant.
    std::vector<std::variant<int, double, std::string>> multiTypeVector;

    //Add eleemnts.
    multiTypeVector.push_back(8);
    multiTypeVector.push_back(8.0);
    multiTypeVector.push_back("test");
    multiTypeVector.push_back("test more");

    //Testing :: print elements.
    for (auto element : multiTypeVector)
        std::visit([](auto arg) {std::cout << arg << std::endl; }, element);
Pavan Chandaka
  • 11,671
  • 5
  • 26
  • 34
1

If the element you store would be a boost::any or boost::variant then you can indirectly store heterogeneous data.

dalle
  • 18,057
  • 5
  • 57
  • 81