Search code examples
c++templatestuplesvariadic-templatesclass-template

Variadic Templated class


Is there a way in C++ to create a templated class that takes any number of arguments in the constructor and can get those when needed?

Example:

#include <string>

template<size_t Size, typename... Types>
class Container
{
public:
    Container(Types... args) 
    {
        // add checks to see if Size = no args
    }

    void get(Types&... args) 
    {
        // getter method
    }
};

int main() 
{
    Container<3, int, double, std::string> container(10, 2.5, "smth");
    int a{};
    double b{};
    std::string c {};
    container.get(a, b, c);
    // expect a = 10, b = 2.5, c = "smth"
    return 0;
}

Solution

  • Yes, a solution to your problem already exists in the form of std::tuple:

    // note: size_t parameter isn't needed, we can simply get the size using
    //       sizeof...(Types)
    template<typename... Types>
    class Container {
    public:
        std::tuple<Types...> stuff;
    
        // note: in practice, this should use forwarding references and
        //       std::forward instead of taking everything by value
        Container(Types... args) : stuff(args...) {}
    
        void get(Types&... args) {
            // std::tie creates a tuple of references, letting us
            // assign all members at once
            std::tie(args...) = stuff;
        }
    };
    

    You can implement a container that wraps std::tuple, but it doesn't really provide any utility that std::tuple doesn't yet, so we can use it directly:

    int main() {
        std::tuple<int, double, std::string> container(10, 2.5, "smth");
    
        // not so good: decompose using three variables and std::tie
        int a {};
        double b {};
        std::string c {};
        std::tie(a, b, c) = container;
    
        // better: use structured bindings (C++17)
        auto [aa, bb, cc] = container;
    }
    

    Keep in mind that outside of generic programming, you're almost always better off creating your own struct, with meaningful names for the type and the members.

    // aggregate type
    struct Container {
        int a;
        double b;
        std::string c;
    };
    
    int main() {
        Container container{10, 2.5, "smth"};
        // structured bindings also work for aggregate types (C++17)
        auto [a, b, c] = container;
    }