To extract data from a template parameter pack, we usually do pattern matching in template.
Firstly, we create a class template At
but without contents. Its template parameters are supposed to be an index, and an instance of HeterogenousValueList
. This class template will be used like a function to access information in the list.
template <int Index, class ValueList>
struct At;
Next, we create a specialization of At
. This is where pattern matching is used. Through pattern matching, the first element of the list will become u
. The rest of the list will be vs
. If the index is 0
, u
can be accessed through the static member value
. Note that vs
can be an empty list, so this also covers the case that u
being the last of the list.
template <auto u, auto... vs>
struct At<0, HeterogenousValueList<u, vs...>>
{
static constexpr auto value = u;
};
What if the index is not 0? We shift the list and decrement the index by 1, and pass them into At
again. In other words, this is a template recursion.
template <int Index, auto u, auto... vs>
struct At<Index, HeterogenousValueList<u, vs...>>
{
static constexpr auto value = At<Index - 1, HeterogenousValueList<vs...>>::value;
};
Now, we can try to use it: https://godbolt.org/g/51dpH8
int main()
{
volatile auto value0 = At<0, MyList1>::value;
volatile auto value1 = At<1, MyList1>::value;
volatile auto value2 = At<2, MyList1>::value;
// volatile auto value3 = At<-1, MyList1>::value;
// volatile auto value4 = At<3, MyList1>::value;
}
I use volatile variable so that the compiler does not optimize the effect away and you can see the effect in the assembly listing.
And one more great thing: the compiler checks the bound! We usually don't have bound check for run-time array for run-time efficiency reason. But this is a compile-time list. The compiler can do it for us!
Actually, there is a simpler implementation. This time, we use constexpr-if in a function template. But the idea of pattern matching and template recursion remain the same.
template <int Index, auto u, auto... vs>
auto at(HeterogenousValueList<u, vs...>)
{
if constexpr (Index == 0)
return u;
else
return at<Index - 1>(HeterogenousValueList<vs...>{});
}
And this time, when we use it, we need to instantiate MyList1
into an object.
https://godbolt.org/g/CA3VHj
int main()
{
volatile auto value0 = at<0>(MyList1{});
volatile auto value1 = at<1>(MyList1{});
volatile auto value2 = at<2>(MyList1{});
// volatile auto value3 = at<-1, MyList1>::value;
// volatile auto value4 = at<3, MyList1>::value;
}