0

I was writing a short snippet of code to figure out how I would store different template specializations into one data structure (e.g. vector). I'm aware of tuple but that's not good because I want to be able to append specializations after the tuple was constructed.

Below is a snippet of code that I came up with. In short, my idea was to have every template specialization inherit from a common class Element and then store such instances into a vector<Element>. That worked, but now when I access that data from vector, I access it as an Element. I need some way to find out which Element is paired with which template specialization.

Using typeid directly on elements from the vector returns the base type. I tried to work around that by having a member function runtime_type return that information from the subclass. Unfortunately, it's not working because the member function from SparseSet<T> is not overriding the virtual member function from the base class. I'm not sure why.

#include <vector>
#include <iostream>
#include <tuple>
#include <typeinfo>
#include <algorithm>

class Element
{
public:
    virtual const std::type_info& runtime_type()
    {
        std::cout << " Called base ";
        return typeid(*this);
    }

    virtual ~Element() = default;
};

template<class T>
class SparseSet : public Element
{
public:
    T param;

    const std::type_info& runtime_type() override
    {
        std::cout << " Called derived ";
        return typeid(*this);
    }
};

class Manager
{
public:
    std::vector<Element> elements;

    template<class T>
    bool has()
    {
        const auto it = std::find_if(elements.begin(), elements.end(), [](auto &element) {
            std::cout << "typeid(element) = " << element.runtime_type().name() << std::endl;
            std::cout << "typeid(T) = " << typeid(T).name() << std::endl;
            return typeid(element) == typeid(T); 
        });
        return it != elements.end();
    }
};

int main()
{   
    SparseSet<int> ss_int;
    ss_int.param = 3;

    SparseSet<char> ss_char;
    ss_char.param = 'a';

    Manager manager;

    manager.elements.push_back(ss_int);
    manager.elements.push_back(ss_char);

    std::cout << manager.has<SparseSet<int>>() << std::endl;

    return 0;
}

When I run the snippet above, using the online compiler coliru (g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out), I get the following output.

typeid(element) =  Called base 7Element
typeid(T) = 9SparseSetIiE
typeid(element) =  Called base 7Element
typeid(T) = 9SparseSetIiE
0
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Spidey
  • 894
  • 1
  • 13
  • 29
  • Can you explain the situation and after that I'll close the question myself if I agree it's a duplicate? EDIT: I did read the link you provided but all I got from it was "lost information when you view something as base class". – Spidey Jun 24 '19 at 15:24
  • @Spidey: "*Can you explain the situation*" The situation is, you're slicing the object. – Nicol Bolas Jun 25 '19 at 00:27
  • 2
    @Spidey: Your `vector` stores `Element`. C++ can't store `Element` subclasses in a `vector`, they're always just `Element`. When you `push_back` a `SparseSet`, it slices it down to an `Element`. If you want to store "`Element` or a subclass of `Element`, you're probably best off making a `vector` of `std::unique_ptr`, which is a bit more of a pain (you'll have to wrap on insert, use dereferencing after indexing to do anything useful), but since pointers (and references) can refer to parent or child classes, it'll allow your child class to function as itself. – ShadowRanger Jun 25 '19 at 00:35
  • Thank you @ShadowRanger for a proper answer. – Spidey Jun 25 '19 at 13:27

1 Answers1

1
std::vector<Element> elements;

This means that elements is a vector of instances of the class Element, period. When a vector allocates memory, for example, it allocates contiguous space leaving sizeof(Element) for each instance. How can that be used to hold an instance of SparseSet?

You may want to use a std::vector<std::unique_ptr<Element>>, since a unique_ptr<Element> can hold a pointer to a SparseSet.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • And this how a question should be answered. Not "you're slicing objects" but rather explaining how exactly am I doing it. Thank you David and ShadowRanger. – Spidey Jun 25 '19 at 13:20