Search code examples
c++c++17variadic-templates

How to mutate variadic arguments of a template


I'm trying to create a struct of arrays:

auto x = MakeMegaContainer<StructA, StructB, StructC>();

Which I want to, at compile time, produce a structure like:

struct MegaContainer {
    std::tuple< Container<StructA>, Container<StructB>, Container<StructC> > Data;
};

The creation method is non-negotiable, and I think tuples are the best way to go, as later I could access one container's struct elements with std::get<StructA>(x)[index], if my container allows [].

I tried doing something like:

    template<typename... LilStructs>
    class StructStorage {
    public:
        template<typename OneStruct>
        OneStruct& GetStruct(const uint64_t& id) {
            return std::get<OneStruct>(m_Storage).Get(id);  // Get is defined for MyContainer
        }

        template<typename OneStruct>
        bool ContainsStruct(const uint64_t& id) {
            return std::get<OneStruct>(m_Storage).Contains(id);  // Contains is defined for MyContainer
        }

    private:
        std::tuple<MyContainer<LilStructs>...> m_Storage;
    };

But I don't think this works. I'm not sure how to take the variadic arguments and "wrap" them with a container

What should I do instead?

Follow up question: The MyContainer also takes additional arguments that customise it, like a max size. Is there a nice way of passing that, something like template<typename... LilStructs, uint64_t MAX_SIZE=4096>?

Here's a stripped down version of the container for a minimal reproducible example:

template<typename T_elem, typename  T_int = uint64_t, T_int MAX_SIZE = 4096>
class MyContainer{
public:
    MyContainer() = default;
    ~MyContainer() = default;

    bool Contains(const T_int& id) const;

    T_elem& operator[](const T_int& id);

    T_elem& Get(const T_int& id);

private:
    std::map<T_int, T_elem> m_Map;
    T_int m_Size = 0;
};

template<typename T_elem, typename  T_int, T_int MAX_SIZE>
bool  MyContainer<T_elem, T_int, MAX_SIZE>::Contains(
    const T_int& id
) const {
    return m_Map[id] < m_Size;
}

template<typename T_elem, typename  T_int, T_int MAX_SIZE>
T_elem& MyContainer<T_elem, T_int, MAX_SIZE>::operator[](const T_int& id) {
    return m_Map[id];
}

template<typename T_elem, typename  T_int, T_int MAX_SIZE>
T_elem& MyContainer<T_elem, T_int, MAX_SIZE>::Get(const T_int& id) {
    return operator[](id);
}

When I try to compile:

void Test() {
    struct SA { int x; };
    struct SB { float x; };
    struct SC { char x; };
    auto& tests = StructStorage <SA, SB, SC>();

    bool x = !tests.ContainsStruct<SA>(5);

}

I get the error: c:\...\test.h(18): error C2039: 'Contains': is not a member of 'Trial::SA' As I said, I know the error is in the std::tuple<MyContainer<LilStructs>...> m_Storage; line, and the error is indpendent of the implementation of MyContainer (provided that Contains and Get are implemented), but I do not know what to replace it with, or how to achieve the functionality I'm looking for (which was already described).


Solution

  • Look at this part:

    template <typename OneStruct>
    OneStruct& GetStruct(const uint64_t& id) {
        return std::get<OneStruct>(m_Storage).Get(id);  // Get is defined for MyContainer
    }
    
    template <typename OneStruct>
    bool ContainsStruct(const uint64_t& id) {
        return std::get<OneStruct>(m_Storage).Contains(id);  // Contains is defined for MyContainer
    }
    

    The m_Storage tuple is a tuple of MyContainer<LilStructs>...s and not LilStructs...s so what you actually want to do is this:

    template <typename OneStruct>
    OneStruct& GetStruct(const uint64_t& id) {
        return std::get<MyContainer<OneStruct>>(m_Storage).Get(id);  // Get is defined for MyContainer
        //              ^^^^^^^^^^^^^^^^^^^^^^
    }
    
    template <typename OneStruct>
    bool ContainsStruct(const uint64_t& id) {
        return std::get<MyContainer<OneStruct>>(m_Storage).Contains(id);  // Contains is defined for MyContainer
        //              ^^^^^^^^^^^^^^^^^^^^^^
    }
    

    Also, your Contains() function is wrong. Use this:

    template <typename T_elem, typename T_int, T_int MAX_SIZE>
    bool MyContainer<T_elem, T_int, MAX_SIZE>::Contains(const T_int& id) const {
        return m_Map.find(id) != m_Map.end();
    }
    

    Full working code:

    #include <cstdint>
    #include <cassert>
    #include <tuple>
    #include <map>
    
    template <typename T_elem, typename T_int = uint64_t, T_int MAX_SIZE = 4096>
    class MyContainer{
    public:
        MyContainer() = default;
        ~MyContainer() = default;
    
        bool Contains(const T_int& id) const;
    
        T_elem& operator[](const T_int& id);
    
        T_elem& Get(const T_int& id);
    
    private:
        std::map<T_int, T_elem> m_Map;
        T_int m_Size = 0;
    };
    
    template <typename T_elem, typename T_int, T_int MAX_SIZE>
    bool MyContainer<T_elem, T_int, MAX_SIZE>::Contains(const T_int& id) const {
        return m_Map.find(id) != m_Map.end();
    }
    
    template <typename T_elem, typename T_int, T_int MAX_SIZE>
    T_elem& MyContainer<T_elem, T_int, MAX_SIZE>::operator[](const T_int& id) {
        return m_Map[id];
    }
    
    template <typename T_elem, typename T_int, T_int MAX_SIZE>
    T_elem& MyContainer<T_elem, T_int, MAX_SIZE>::Get(const T_int& id) {
        return operator[](id);
    }
    
    template <typename ...LilStructs>
    class StructStorage {
    public:
        template <typename OneStruct>
        OneStruct& GetStruct(const uint64_t& id) {
            return std::get<MyContainer<OneStruct>>(m_Storage).Get(id);
        }
    
        template <typename OneStruct>
        bool ContainsStruct(const uint64_t& id) {
            return std::get<MyContainer<OneStruct>>(m_Storage).Contains(id);
        }
    
    private:
        std::tuple<MyContainer<LilStructs>...> m_Storage;
    };
    
    int main() {
        struct SA { int x; };
        struct SB { float x; };
        struct SC { char x; };
        auto tests = StructStorage<SA, SB, SC>();
    
        assert(!tests.ContainsStruct<SA>(5));
    }
    

    Demo