从异构列表中提取数据

Extract data from heterogeneous list

本文关键字:提取 数据 列表 异构      更新时间:2023-10-16

我看到了这个关于模板参数中自动的优点的答案。

请考虑以下异构列表:

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

现在我已经声明了一个名为MyList1的类型。如何从该类型(即42、'x'或13u(中提取存储的数据?

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

首先,我们创建了一个没有内容的类模板At。它的模板参数应该是一个索引和HeterogenousValueList的一个实例。该类模板将像函数一样用于访问列表中的信息。

template <int Index, class ValueList>
struct At;

接下来,我们创建一个At的专业化。这就是使用模式匹配的地方。通过模式匹配,列表的第一个元素将变为u。列表的其余部分将是vs。如果索引为0,则可以通过静态成员value访问u。注意,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;
}

当你提到"玩"answers"通过操纵来学习"时,你可能会对几种选择感兴趣。

第一种解决方案:转换为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个类型/元素"版本。这种业务的一个方面是编译时性能:特别是递归策略据说会导致编译时间过长。

我最喜欢的非递归策略是Boost.Hana中也使用的策略。如果你喜欢视频解释,你可以从01小时12分钟开始观看Louis Dionne(Boost.Haa作者(的两分钟演讲"Metaprogramming for the brave"。这个想法是使用多重继承。在您的示例中,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