C++ How to create a heterogeneous container
Asked Answered
F

4

6

I need to store a series of data-points in the form of (name, value), where the value could take different types.

I am trying to use a class template for each data-point. Then for each data-point I see, I want to create a new object and push it back into a vector. For each new type, I need to create a new class from the template first. But I can not store the objects created in any vector, since vectors expect the same type for all entries. The types I need to store can not be fitted in a inheritance hierarchy. They are unrelated. Also there can be more types created in future, and I do not want to change the storage service for each new type. Is there a way to create a heterogeneous container to store these entries? Thank you!

Formenti answered 9/7, 2010 at 11:15 Comment(2)
It shows one way of dealing with runtime polymorphism. I guess it is related. youtu.be/vxv74Mjt9_0?t=16m8sCimon
Possible duplicate of How can I store objects of differing types in a C++ container?Stroud
J
7

The boost library has probably what you're looking for (boost::any). You can roll your own using a wrapped pointer approach if you cannot use boost...

Jaw answered 9/7, 2010 at 11:21 Comment(1)
Thanks! I think "any" will work! I also found a tutorial on this based on boost::any. Here it is, if anybody needs this! devx.com/cplus/10MinuteSolution/29757/1954Formenti
C
10

C++17 and later.

std::any allows to hold any type, although it requires knowing the type that was stored to retrieve it.

If you have a set of known types, however, you may prefer std::variant:

using variant_type = std::variant<Foo, Bar, Joe>;

int func(variant_type const& v) // not template
{
    auto const visitor = [](auto const& t)
    {
        if constexpr (std::is_same_v<Foo const&, decltype(t)>)
        {
            return t.fooish();
        }
        else
        {
            return t.barjoeish();
        }
    };

    return std::visit(visitor, v);
}

A useful trick for quickly defining visitors:

template <typename... Ts> struct overload : Ts...
{
    overload(Ts... aFns) : Ts(aFns)... {}
    using Ts::operator()...;
};
template <typename... Ts> overload(Ts...) -> overload<Ts...>;

//  Used as
auto const visitor = overload(
    [](Foo const& foo) { return foo.fooish(); },
    [](auto const& other) { return other.joebarish(); }
);

return std::visit(visitor, variant);

Pre-C++17.

boost::any has already been recommended, however it's for anything, so you can't expect much from it.

If you know the various types ahead of time, you're better using boost::variant.

typedef boost::variant<Foo, Bar, Joe> variant_type;

struct Print: boost::static_visitor<>
{
  void operator()(Foo const& f) const { f.print(std::cout); }

  template <class T>
  void operator()(T const& t) const { std::cout << t << '\n'; }
};

void func(variant_type const& v) // not template
{
  boost::apply_visitor(Print(), v); // compile-time checking
                                    // that all types are handled
}
Cecilececiley answered 9/7, 2010 at 16:19 Comment(2)
Exactly! It's worth noting as of C++17 those are part of the standard library.Voidable
@Vorac: Good point, answer updated and extended.Cecilececiley
J
7

The boost library has probably what you're looking for (boost::any). You can roll your own using a wrapped pointer approach if you cannot use boost...

Jaw answered 9/7, 2010 at 11:21 Comment(1)
Thanks! I think "any" will work! I also found a tutorial on this based on boost::any. Here it is, if anybody needs this! devx.com/cplus/10MinuteSolution/29757/1954Formenti
H
4

The problem with containers like this is that when you want to access something in the container, you have to determine its type and then cast it to the actual type somehow. This is ugly, inefficient and error-prone, which is why the #1 choice in C++ is to use inheritance, unless you have a very good reason not to - something I've never actually come across in my C++ career.

Homecoming answered 9/7, 2010 at 11:25 Comment(5)
Thanks Neil!...So these data-types are basically all over the place, they are a set of knobs if you like. They can be long int, string, bool, and so on :( Can't Use inheritance..Formenti
The problem with vectors and C++ classes is that you cannot declare a vector of Base and then put instances of Derived inside. Because of copy philosophy of C++ any such implementation for std::vector (or any other std::container) must use pointers (eventually wrapped ones).Jaw
@Formenti Actually you can use inheritance, that is how boost::any works, it erases the type into a private base which has template derived type that contains the values, this is all hidden inside of boost::any where it's constructor/assignment operator does the type erasing (they are templates). However I suggest your reconsider your design, do really need this.Stereo
Thanks snk_kid! Let me use boost::any for now. And you are right, this is not a good design. It is a hack I need to do, because I am working on a big architectural simulator, and changing the design to get this implemented properly will require a lot of permissions and time, and I am just doing a specific enhancement as part of my internship. Eventually this will be fixed the proper way, but I will be back to school by then!Formenti
@Formenti If you have a set of known types I would prefer to use boost::variant and use static visitors, much better.Stereo
U
0

I was thinking that you could just have a Pair(type, void*) and write your own pop function that casts the void* depending upon the type describe in the pair and then shove these into whatever container catches your eye.

Undress answered 9/7, 2010 at 11:24 Comment(1)
as mentioned by Neil though, error prone and inefficient, I would also not recommend this. Either follow Neil's suggestion and just use inheritance or take a look at boost::any as aforementioned by 6502.Undress

© 2022 - 2024 — McMap. All rights reserved.