0

I have the following situation:

class A {
public:
 int a;

 // Just for RTTI
 virtual ~A() = default;
}

class B : public A {
public:
 int b;
}

And then I create an std::unordered_map<int, A>, where I want to be able to store both As and Bs, and get back to their original representation.

I always know whether the element corresponds to an A or a B from context at the point of find() by looking just at the key.

However, if I run the following program:

#include <unordered_map>
#include <iostream>

using namespace std;


class A {
public:
 int a{};

 A() = default;
 A(int a) : a(a){}

 // Just for RTTI
 virtual ~A() = default;
};

class B : public A {
public:
 B(int a, int b) : A(a), b(b) {}
 int b;
};


int main()
{
    std::unordered_map<int, A> m;
    m[1] = A(4);
    m[2] = B(2,5);

    if (dynamic_cast<B *>(&m.at(2)) == nullptr) {
        std::cout << "FAIL!" << std::endl;
    }
}

I get a "FAIL" printed in my screen. This was not the behavior I expected; how can I get this to work?

Misguided
  • 1,302
  • 1
  • 14
  • 22

2 Answers2

1

What you've run into is object slicing -- in particular, your unordered_set<int, A> is able to contain objects of type A only. When you try to place an object of type B into it, it compiles, but only the superclass-part of the B object is actually copied into the data structure -- the subclass part (i.e. the part of the B object that is not part of the A superclass) is "sliced off" and discarded, and what is stored in the set is just another A.

If you want your unordered_set to contain values of different subclasses, then the only way to do it is to store the value-objects indirectly via some sort of pointer, instead; for example, you could use a unordered_set<int, A*> instead, or better yet a unordered_set<int, shared_ptr<A> > (so that you'll be protected from memory leaks). Then you'll need to allocate each A/B object on the heap before inserting it into the set.

(An alternative approach would be to keep a separate unordered_set for each subclass, e.g. have a unordered_set<int, A> and a unordered_set<int, B>. Of course the downside to that is it complicates your calling code)

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
1

Just in case anyone runs into this in the future, another road you can take is to use std::variant and keep the single hash table without -at least directly- using pointers as Jeremy suggested.

Misguided
  • 1,302
  • 1
  • 14
  • 22