C++ Push Multiple Types onto Vector

25,974

Solution 1

In order to do that, you'll definitely need a wrapper class to somehow conceal the type information of your objects from the vector.

It's probably also good to have this class throw an exception when you try to get Type-A back when you have previously stored a Type-B into it.

Here is part of the Holder class from one of my projects. You can probably start from here.

Note: due to the use of unrestricted unions, this only works in C++11. More information about this can be found here: What are Unrestricted Unions proposed in C++11?

class Holder {
public:
    enum Type {
        BOOL,
        INT,
        STRING,
        // Other types you want to store into vector.
    };

    template<typename T>
    Holder (Type type, T val);

    ~Holder () {
        // You want to properly destroy
        // union members below that have non-trivial constructors
    }

    operator bool () const {
        if (type_ != BOOL) {
           throw SomeException();
        }
        return impl_.bool_;
    }
    // Do the same for other operators
    // Or maybe use templates?

private:
    union Impl {
        bool   bool_;
        int    int_;
        string string_;

        Impl() { new(&string_) string; }
    } impl_;

    Type type_;

    // Other stuff.
};

Solution 2

The objects hold by the std::vector<T> need to be of a homogenous type. If you need to put objects of different type into one vector you need somehow erase their type and make them all look similar. You could use the moral equivalent of boost::any or boost::variant<...>. The idea of boost::any is to encapsulate a type hierarchy, storing a pointer to the base but pointing to a templatized derived. A very rough and incomplete outline looks something like this:

#include <algorithm>
#include <iostream>

class any
{
private:
    struct base {
        virtual ~base() {}
        virtual base* clone() const = 0;
    };
    template <typename T>
    struct data: base {
        data(T const& value): value_(value) {}
        base* clone() const { return new data<T>(*this); }
        T value_;
    };
    base* ptr_;
public:
    template <typename T> any(T const& value): ptr_(new data<T>(value)) {}
    any(any const& other): ptr_(other.ptr_->clone()) {}
    any& operator= (any const& other) {
        any(other).swap(*this);
        return *this;
    }
    ~any() { delete this->ptr_; }
    void swap(any& other) { std::swap(this->ptr_, other.ptr_); }

    template <typename T>
    T& get() {
        return dynamic_cast<data<T>&>(*this->ptr_).value_;
    }
};

int main()
{
    any a0(17);
    any a1(3.14);
    try { a0.get<double>(); } catch (...) {}
    a0 = a1;
    std::cout << a0.get<double>() << "\n";
}

Solution 3

As suggested you can use various forms of unions, variants, etc. Depending on what you want to do with your stored objects, external polymorphism could do exactly what you want, if you can define all necessary operations in a base class interface.

Here's an example if all we want to do is print the objects to the console:

#include <iostream>
#include <string>
#include <vector>
#include <memory>

class any_type
{
public:
   virtual ~any_type() {}
   virtual void print() = 0;
};

template <class T>
class concrete_type : public any_type
{
public:
   concrete_type(const T& value) : value_(value)
   {}

   virtual void print()
   {
      std::cout << value_ << '\n';
   }
private:
   T value_;
};

int main()
{
   std::vector<std::unique_ptr<any_type>> v(2);

   v[0].reset(new concrete_type<int>(99));
   v[1].reset(new concrete_type<std::string>("Bottles of Beer"));

   for(size_t x = 0; x < 2; ++x)
   {
      v[x]->print();
   }

   return 0;
}
Share:
25,974
Oliver Spryn
Author by

Oliver Spryn

My first programming experience was a trial-by-fire adventure. It wasn't a "Hello World" application but a full-fledged, web-based learning management system. This exposure galvanized my career choice and prepared me to face a myriad of challenges head-on. My unconventional start continues to yield tangible successes in nearly every discipline I touch, whether on the technical, operational, or business level. Since then, I've been on a mission to make technology work seamlessly and feel invisible. I have delivered this continued success with a daily, sleeves-rolled-up approach. Whenever my superiors need their most complex projects to be built and flawlessly delivered, they ask my team. I keep a good rapport with my colleges and managers so that working as a team is flawless. Their confidence in me has enabled me to be at the forefront of the engineering and design efforts necessary to bring applications from 0 to over 600k users. Building projects of this quality becomes a craft. The concepts I've worked to develop, discover, and distill have worked so well that they have been featured on the droidcon blog, home of Europe's foremost Android conference. Whether my work is published on a prominent blog or neatly packed inside of some back-end service, I ensure that I conduct each of these projects with my full measure of integrity. This trait is essential in delivering healthy projects on time, and it sets the good projects apart from the great ones. Prominent Skills: Android, Kotlin, Gradle, Azure Media Services, Azure Functions, Cloudflare Workers, Adobe Premiere Pro, Adobe Illustrator, All Forms of Communication, Videography, Listening, Critical Thinking

Updated on July 05, 2022

Comments

  • Oliver Spryn
    Oliver Spryn almost 2 years

    Note: I know similar questions to this have been asked on SO before, but I did not find them helpful or very clear.

    Second note: For the scope of this project/assignment, I'm trying to avoid third party libraries, such as Boost.

    I am trying to see if there is a way I can have a single vector hold multiple types, in each of its indices. For example, say I have the following code sample:

    vector<something magical to hold various types> vec;
    int x = 3;
    string hi = "Hello World";
    MyStruct s = {3, "Hi", 4.01};
    
    vec.push_back(x);
    vec.push_back(hi);
    vec.push_back(s);
    

    I've heard vector<void*> could work, but then it gets tricky with memory allocation and then there is always the possibility that certain portions in nearby memory could be unintentionally overridden if a value inserted into a certain index is larger than expected.

    In my actual application, I know what possible types may be inserted into a vector, but these types do not all derive from the same super class, and there is no guarantee that all of these types will be pushed onto the vector or in what order.

    Is there a way that I can safely accomplish the objective I demonstrated in my code sample?

    Thank you for your time.

  • Mark B
    Mark B over 11 years
    I'm pretty sure you can't put std::string in a union because of 9.5/1 which says you can't do that if it has a non-trivial constructor, copy constructor, destructor, or copy assignment operator.
  • Jimmy Lu
    Jimmy Lu over 11 years
    you can now in c++11, which has all the goodness :p You can refer to it at wikipedia.
  • Mark B
    Mark B over 11 years
    You should probably note that this is a C++11-only solution in your answer then, as well as noting that you'll have to use placement new and explicit destruction to use the string member safely.
  • Jimmy Lu
    Jimmy Lu over 11 years
    modified my answer to state it is C++11-only :-)
  • Moo-Juice
    Moo-Juice about 11 years
    +1, good answer. Though remember in data, clone() should be data<T>* clone() const as it is covariant :) But this is arguably the better answer than the accepted answer.
  • Yohaï-Eliel Berreby
    Yohaï-Eliel Berreby almost 10 years
    Did someone profile this code? This solution seems clean but is it faster or slower than the traditional polymorphism + virtual or dynamic_cast<>?
  • David Doria
    David Doria over 8 years
    Is there a way to have, rather than print() a T get() function where you can somehow to v[x]::T result = v[x].get()?
  • chutsu
    chutsu about 7 years
    Recommend bolding this part of your description "if you can define all necessary operations in a base class interface", this is vital else the solution will not work. It is worth noting that the same applies to member variables?
  • Matthaeus Gaius Caesar
    Matthaeus Gaius Caesar about 2 years
    Having to know the type to fetch the data makes this solution unhelpful for many applications - i.e. using a0.get<double>(). Why not just use a void pointer if we are assuming we know the types?
  • Dietmar Kühl
    Dietmar Kühl about 2 years
    @MatthaeusGaiusCaesar The difference may be minor but you can check what type is actually in there, eg., using a dynamic_cast>. You can do better if you are so inclined, eg., by dispatching the type check into a virtual function yielding a std::type_info const&. The Boost any or std::any (added with C++17) do effectively that and it isn’t hard to add to the short demo implementation. The code is just a basic draft and the answer says so. To make it nice you’d need to add things - or use std::any which didn’t exist when the answer was written.