c++templatescoliru

Why is virtual member function being called from base class instead of a subclass


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

Solution

  • 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.