2

I was looking at this answer about the advantages of auto in template parameter.

Please consider the following heterogeneous list:

template <auto ... vs> struct HeterogenousValueList {};
using MyList1 = HeterogenousValueList<42, 'X', 13u>;

Now I've declared a type named MyList1. How can I extract the stored data from this type (i.e., 42, 'x' or 13u)?

Donotalo
  • 12,748
  • 25
  • 83
  • 121
  • 1
    Depends on what do you want to do with that data. To pass it around, you can just use `vs` just like any other non-type variadic pack. You can expand it into some (possibly) variadic function with `vs...` and then proceed just like with a function, etc. – Bartek Banachewicz Jul 26 '18 at 06:44
  • 1
    I think this question is too broad. Please specify a little bit more exactly what do you mean by "extract stored data". What do you want to do with `vs`? Print it? Pass it to some other function? – geza Jul 26 '18 at 06:47
  • Just playing with this C++17 feature. Learning it by manipulation. :) – Donotalo Jul 26 '18 at 11:08

2 Answers2

7

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;
}
  • For convenience of your consumers, you might want to use `std::integral_constant` in your solution. And maybe also template-templates for generality. – Deduplicator Jul 26 '18 at 07:34
2

As you mention "playing" and "learning it by manipulation", you may be interested in several alternatives.

First solution: convert to std::tuple and then std::get

The std::tuple, which is a container for heterogeneous values, can be utilized to access the elements of your heterogeneous value list. This is a simple two-step process:

  1. convert HeterogenousValueList<42, 'X', 13u>{} to std::tuple<int, char, unsigned>{42, 'X', 13u}
  2. access the tuple's values at the desired positions by std::get

Complete C++17 example:

#include <type_traits>
#include <tuple>

template<auto... vs>
struct HeterogenousValueList {};

template<int i, auto... vs>
constexpr auto get(HeterogenousValueList<vs...>) {
  constexpr std::tuple tuple{vs...};// class-template argument deduction

  static_assert(std::is_same<
    decltype(tuple), const std::tuple<int, char, unsigned>
  >{});

  return std::get<i>(tuple);
}

int main() {
  using MyList1 = HeterogenousValueList<42, 'X', 13u>;

  constexpr auto at1 = get<1>(MyList1{});
  static_assert(at1 == 'X');
  static_assert(std::is_same<decltype(at1), const char>{});
}

Second solution: wrap in types for std::tuple_element_t

Another useful idiom is to wrap non-type template parameters in a single empty type. In your example, this allows to make use of std::tuple_element_t, which can yield the nth type of a variadic pack:

#include <type_traits>
#include <tuple>

template<auto... vs>
struct HeterogenousValueList {};

template<auto v_>
struct SingleValue {// similar to std::integral_constant, but uses auto
  static constexpr auto v = v_;
};

template<int i, auto... vs>
constexpr auto get(HeterogenousValueList<vs...>) {
  using TupleOfSingleValues = std::tuple<SingleValue<vs>...>;
  using SingleValueAtPosition = std::tuple_element_t<i, TupleOfSingleValues>;
  return SingleValueAtPosition::v;
//  return std::tuple_element_t<i, std::tuple<SingleValue<vs>...>>::v;// same
}

// same `main` as first solution

Third solution: Implement your own logic

There are several ways to implement your own version for "getting the nth type/element". One aspect in this business is the compile-time performance: particularly the recursive strategies are said to cause long compilation times.

My favorite non-recursive strategy is the one which is also used in Boost.Hana. If you prefer a video explanation you can watch two minutes of the talk "Metaprogramming for the brave" by Louis Dionne (Boost.Hana author) starting at 01 h 12 min. The idea is to use multiple inheritance. In your example, HeterogenousList<42, 'X', 13u> can have the base classes IndexedValue<0, 42>, IndexedValue<1, 'X'>, and IndexedValue<2, 13u>. Then you can pass the HeterogenousList<42, 'X', 13u> to a templated get function that takes a, say, const IndexedValue<1, [[DEDUCED]]>& as argument:

#include <type_traits>
#include <utility>

template<std::size_t i, auto v_>
struct IndexedValue {
  static constexpr auto v = v_;
};

template<class Is, auto... vs>
struct IndexedHeterogenousList;

template<std::size_t... is, auto... vs>
struct IndexedHeterogenousList<
  std::index_sequence<is...>,// partial specialization to extract the `is`
  vs...
> : IndexedValue<is, vs>...// multiple "variadic" inheritance
{};

template<auto... vs>
struct HeterogenousValueList
  : IndexedHeterogenousList<std::make_index_sequence<sizeof...(vs)>, vs...>
{};

template<std::size_t i, auto v>// `i` must be given; `v` is deduced
constexpr auto get(const IndexedValue<i, v>& iv) {// one base matches
  return v;
}

// same `main` as first solution
Julius
  • 1,816
  • 10
  • 14