从异构列表中提取数据

时间:2018-07-26 06:31:38

标签: c++ templates variadic-templates c++17

我当时在看this answer关于advantages of auto in template parameter的情况。

请考虑以下异构列表:

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

现在,我已经声明了一个名为MyList1的类型。如何从这种类型(即42,“ x”或13u)中提取存储的数据?

2 个答案:

答案 0 :(得分:7)

要从模板参数包中提取数据,我们通常在模板中进行模式匹配。

首先,我们创建一个类模板At,但不包含任何内容。它的模板参数应该是索引和HeterogenousValueList的实例。该类模板将像访问列表中信息的函数一样使用。

template <int Index, class ValueList>
struct At;

接下来,我们创建At的专业化名称。这是使用模式匹配的地方。通过模式匹配,列表的第一个元素将变为u。列表的其余部分将为vs。如果索引为0,则可以通过静态成员u访问value。请注意,vs可以是一个空列表,因此也涵盖了u是列表的最后一个情况。

template <auto u, auto... vs>
struct At<0, HeterogenousValueList<u, vs...>>
{
    static constexpr auto value = u;
};

如果索引不为0,该怎么办?我们将列表移位,并将索引减1,然后将它们再次传递到At中。换句话说,这是模板递归。

template <int Index, auto u, auto... vs>
struct At<Index, HeterogenousValueList<u, vs...>>
{
    static constexpr auto value = At<Index - 1, HeterogenousValueList<vs...>>::value;
};

现在,我们可以尝试使用它: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;
}

我使用了volatile变量,这样编译器就无法优化效果了,您可以在程序集列表中看到效果。

还有一件很棒的事情:编译器检查边界!由于运行时效率的原因,我们通常不需要对运行时数组进行绑定检查。但这是一个编译时列表。编译器可以为我们做到!


实际上,有一个更简单的实现。这次,我们在函数模板中使用constexpr-if。但是模式匹配和模板递归的思想保持不变。

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...>{});
}

这一次,当我们使用它时,我们需要将MyList1实例化为一个对象。

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

答案 1 :(得分:2)

当您提到“玩耍”和“通过操纵来学习它”时,您可能会对几种选择感兴趣。

第一个解决方案:先转换为std::tuple,然后转换为std::get

std::tuple是异构值的容器,可用于访问异构值列表的元素。这是一个简单的两步过程:

  1. HeterogenousValueList<42, 'X', 13u>{}转换为std::tuple<int, char, unsigned>{42, 'X', 13u}
  2. 通过std::get在所需位置访问元组的值

完整的C ++ 17示例:

#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>{});
}

第二个解决方案:包装std::tuple_element_t的类型

另一个有用的习惯用法是将非类型模板参数包装在一个空类型中。在您的示例中,这允许使用std::tuple_element_t,它可以产生可变参数包的第n种类型:

#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

第三种解决方案:实施自己的逻辑

有几种方法可以实现自己的版本以“获取第n个类型/元素”。这项业务的一个方面是编译时性能:尤其是递归策略据说会导致较长的编译时间。

我最喜欢的非递归策略是which is also used in Boost.Hana。如果您喜欢视频解释,可以观看"Metaprogramming for the brave" by Louis Dionne (Boost.Hana author) starting at 01 h 12 min的两分钟演讲。这个想法是使用多重继承。在您的示例中,HeterogenousList<42, 'X', 13u>可以具有基类IndexedValue<0, 42>IndexedValue<1, 'X'>IndexedValue<2, 13u>。然后,您可以将HeterogenousList<42, 'X', 13u>传递给模板化的get函数,该函数以const IndexedValue<1, [[DEDUCED]]>&作为参数:

#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