我当时在看this answer关于advantages of auto in template parameter的情况。
请考虑以下异构列表:
template <auto ... vs> struct HeterogenousValueList {};
using MyList1 = HeterogenousValueList<42, 'X', 13u>;
现在,我已经声明了一个名为MyList1
的类型。如何从这种类型(即42,“ x”或13u)中提取存储的数据?
答案 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
实例化为一个对象。
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
是异构值的容器,可用于访问异构值列表的元素。这是一个简单的两步过程:
HeterogenousValueList<42, 'X', 13u>{}
转换为std::tuple<int, char, unsigned>{42, 'X', 13u}
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