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;
}