前者是我们需要使用的编译期容器(vector也许以后也会成为编译期容器,编译期的动态内存分配已经进入c++20),后者负责把一串数字映射为模板参数包,以便折叠表达式展开。(折叠表达式仍然可以参考cppreference上的解释)
std::index_sequence的一个示例:
using Ints = std::make_index_sequence<5>; // 产生std::index_sequence<0, 1, 2, 3, 4> // 将一串数字传递给模板,重新映射为变长模板参数 template <typename T, std::size_t... Nums> void some_func(T, std::index_sequence<Nums...>) {/**/} some_func("test", Ints{}); // 这时Nums包含<0, 1, 2, 3, 4>这个用法看着很像元编程的惯用法之一的标签分派,但是仔细看的话两者不是同一种技巧,暂时没有发现这种技巧的具体名字,因此我们就暂时称其为“整数序列映射”。
有了这些前置知识,现在可以看实现了:
template <typename TList, typename T> struct IndexOf2; template <typename T, typename... Types> struct IndexOf2<TypeList<Types...>, T> { using Seq = std::make_index_sequence<sizeof...(Types)>; static constexpr int index() { std::array<bool, sizeof...(Types)> buf = {false}; set_array(buf, Seq{}); for (int i = 0; i < sizeof...(Types); ++i) { if (buf[i] == true) { return i; } } return -1; } template <typename U, std::size_t... Index> static constexpr void set_array(U& arr, std::index_sequence<Index...>) { ((std::get<Index>(arr) = std::is_same_v<T, Types>), ...); } }; // 空TypeList单独处理,简单返回-1即可,因为list里没有任何东西自然只能返回-1 template <typename T> struct IndexOf2<TypeList<>, T> { static constexpr int index() { return -1; } };其中index很好理解,首先初始化一个array,随后将参数包的每个参数的状态映射到array里,之后循环找到第一个true的index,整个过程都在编译期进行。
问题在于set_array里,里面究竟发生了什么呢?
首先是我们前面提到的整数序列映射,Index在映射后是{0, 1, 2, ..., len_of(Array) - 1},接着被折叠表达式展开为:
( (std::get<0>(arr) = std::is_same_v<T, Types_0>), (std::get<1>(arr) = std::is_same_v<T, Types_1>), (std::get<2>(arr) = std::is_same_v<T, Types_2>), ..., (std::get<len_of(Array) - 1>(arr) = std::is_same_v<T, Types_(len_of(Array) - 1>)), )真实的展开是类似Arg1, (Arg2, (Arg3, Arg4))这种,为了可读性我把括号省略了,反正在这里执行顺序并不影响结果。
get会返回array中指定的index的内容的引用,因此我们可以对它赋值,Types_N则是从左至右被依次展开的参数,这样不借助递归就将参数包中所有的参数处理完了。
不过本质上方案B还是舍近求远式的杂耍,实用性并不高,但是它充分展示了现代c++给模板元编程带来的可能性。
Append为TypeList添加元素看完前面几个元函数你可能已经觉得有点累了,没事我们看个简单的放松一下。
Append可以在TypeList前添加元素(虽然这个操作严格来说不叫Append,但后面经常要用而且实现类似,所以请允许我把它当作特殊的Append),在TypeList后面添加元素或是其他TypeList中的所有元素。
调用形式如下:
Append<int, TList>::result_type; Append<TList, long>::result_type; Append<TList1, TList2>::result_type;借助变长模板实现起来颇为简单:
template <typename, typename> struct Append; template <typename... TList, typename T> struct Append<TypeList<TList...>, T> { using result_type = TypeList<TList..., T>; }; template <typename T, typename... TList> struct Append<T, TypeList<TList...>> { using result_type = TypeList<T, TList...>; }; template <typename... TListLeft, typename... TListRight> struct Append<TypeList<TListLeft...>, TypeList<TListRight...>> { using result_type = TypeList<TListLeft..., TListRight...>; }; Erase和EraseAll删除元素顾名思义,Erase负责删除第一个匹配的type,EraseAll删除所有匹配的type,它们有着一样的调用形式:
Erase<TList, int>::result_type; EraseAll<TList, long>::result_type;Erase的算法也比较简单,利用了递归,先在本层查找,如果匹配就返回去掉Head的TypeList,否则对剩余的部分继续调用Erase:
template <typename TList, typename T> struct Erase; template <typename Head, typename... Tails, typename T> struct Erase<TypeList<Head, Tails...>, T> { using result_type = typename Append<Head, typename Erase<TypeList<Tails...>, T>::result_type >::result_type; }; // 终止条件1,删除匹配的元素 template <typename... Tails, typename T> struct Erase<TypeList<T, Tails...>, T> { using result_type = TypeList<Tails...>; }; // 终止条件2,未发现要删除的元素 template <typename T> struct Erase<TypeList<>, T> { using result_type = TypeList<>; };注意模板的第一个参数必须是一个TypeList。