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

Answer

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