• Joseph

Variadic Structures

Updated: Apr 18

We can use variadic arguments to create variadic data structures in C++.


template<typename ... T>
struct DS{};

template<typename T, typename ... Args>
struct DS<T, Args ...>
{
    T first;
    DS<Args ...> args; 
};

int main()
{
    struct DS<int, float> obj;
    return 0;
}

In the above example the empty structure definition is a specialisation of the structure, while the second definition gives the variadic structure. The variadic structure object, obj, will have two members. The first member will be an integer and the second member be a float. The members of the structure can be unpacked as follows.

DS<int, float>
   -> int first
   -> DS<float> args
         -> float first
         -> DS<> args
              -> (empty)

We can also add a constructor using the same parameter pack


template<typename ... T>
struct DS{};

template<typename T, typename ... Args>
struct DS<T, Args ...>
{
    DS(const T& first, const Args& ... args)
        : first(first),
          args(args...)
    {}

    T first;
    DS<Args ...> args; 
};

int main()
{
    struct DS<int, float> obj(2, 3.14);
    return 0;
}


The members can be accessed as follows


int main()
{
    struct DS<int, float> obj;
    return 0;

    obj.first = 2;
    obj.args.first = 3.14;
}

As you can imagine accessing the members is not easy and for this, we will have to define a get() function. To implement this get() function we will be using another structure to will help us.


template<size_t index, typename T>
struct HelperStruct{};

template<typename T, typename ... Args>
struct HelperStruct<0, DS<T, Args ... >>
{
    static T get(DS<T, Args...>& obj)
    {
        return obj.first;
    }
};

template<size_t index, typename T, typename ... Args>
struct HelperStruct<index, DS<T, Args ... >>
{
    static auto get(DS<T, Args...>& obj)
    {
        return HelperStruct<index-1, DS<Args ...>>::get(obj.args);
    }
};

The get() inside the struct HelperStruct, decrements the index and then invokes HelperStruct on the rest of the arguments in Args ... . In addition to this there are two specialisations. One is the empty struct and the other is when the index is 0.

The get() function in the HelperStruct is then used inside the get() function in the struct DS as follows


template<size_t index>
auto get()
 {
  	return HelperStruct<index, DS< T, Args...>>::get(*this);
 } 


where this points to the object of struct DS. To better understand what we have done above let us take an example. Suppose we have the following example


DS<int, float> obj;
obj.get<1>()

The call can be unpacked as follows:


HelperStruct<1, DS<int, float>>::get(obj)
    -> HelperStruct<0, DS<float>>::get(obj.rest)
        -> return obj.first

The final program will be as given below


#include<iostream>


template<size_t index, typename T>
struct HelperStruct{};

template<typename ... T>
struct DS{};

template<typename T, typename ... Args>
struct DS<T, Args ...>
{
    DS(const T& first, const Args& ... args)
        : first(first),
          args(args...)
    {}

    template<size_t index>
    auto get()
    {
        return HelperStruct<index, DS< T, Args...>>::get(*this);
    }

    T first;
    DS<Args ...> args; 
};

template<typename T, typename ... Args>
struct HelperStruct<0, DS<T, Args ... >>
{
    static T get(DS<T, Args...>& obj)
    {
        return obj.first;
    }
};

template<size_t index, typename T, typename ... Args>
struct HelperStruct<index, DS<T, Args ... >>
{
    static auto get(DS<T, Args...>& obj)
    {
        return HelperStruct<index-1, DS<Args ...>>::get(obj.args);
    }
};

int main()
{
    struct DS<int, float> obj(2, 3.14);

    std::cout << obj.get<0>() << std::endl;
    std::cout << obj.get<1>() << std::endl;
    
    return 0;
}








4 views0 comments

Recent Posts

See All