Search code examples
c++templatescompile-time

Get list entry of type T at compile-time


I've a container class that stores some objects of an abstract class. In some parts, the program needs to get these objects as there implementation classes. I wrote a generic get function, but it has to loop through all stored objects, which can cost a lot of time. As I know that the container will only have one object of each type I want to solve this issue during compile-time, but don't want to create my own member for each type by hand.

At the moment I don't have trouble with the "wasted" runtime, but I want to learn how to solve this.

My current implementaion:

#include <iostream>
#include <list>

class Abstract
{
public:
    virtual void update() = 0;
};

class ImplA : public Abstract
{
public:
    void update() {std::cout << "implA" << std::endl;}

};

class ImplB : public Abstract
{
public:
    void update() {std::cout << "implB" << std::endl;}

};

class Container
{
public:
    Container(){
        content.push_back(new ImplA);
        content.push_back(new ImplB);
    }

    void update() {
        for (Abstract* obj : content)
            obj->update();
    }

    template<typename T> T* get() const {
        for (Abstract* obj : content) {
            if(dynamic_cast<T*>(obj) != nullptr)
                return dynamic_cast<T*>(obj);
        }
        return nullptr;
    }

private:
    std::list<Abstract*> content;
};

int main()
{
    Container* container = new Container();
    container->get<ImplA>()->update();
    container->get<ImplB>()->update();
    return 0;
}

My ideas on how to solve this:

  • create an own member and get function for each type
  • Instead of using a list use a map and use the typeinfo.name as key (but that's a runtime solution and would be a bad idea)
  • Implement a list of types (e.g. enum types {implA, implB}) and use this as map-key. Here I would have to create a new entry for each type.
  • Using something like a compile-time map, which could use a type as a key.

Thanks in advance!


Solution

  • If your objects are always inserted together, then your problem has already been solved by std::tuple:

    #include <iostream>
    #include <tuple>
    
    template <class... Ts>
    struct Container : std::tuple<Ts...> {
        using std::tuple<Ts...>::tuple;
    
        void update() {
            std::apply([](auto &... objects){ (objects.update(), ...); }, *this);
        }
    };
    
    namespace std {
        template <class... Ts>
        struct tuple_size<Container<Ts...>> : tuple_size<tuple<Ts...>> { };
    }
    
    template <class... Ts>
    Container(Ts...) -> Container<Ts...>;
    

    Container supports std::get through implicit conversion to std::tuple and the specialization of std::tuple_size.

    See it Live on Coliru (tweaked for its pre-C++17 GCC :/)