C++ 自定义类型列表 (Type List):Variadic Templates 实现类型操作与映射
大家好,今天我们来深入探讨一个 C++ 中高级技巧:利用 Variadic Templates 实现自定义类型列表 (Type List),并进行类型操作和映射。类型列表是一个在编译时存储类型序列的数据结构,它允许我们在编译时对类型进行各种操作,如筛选、转换、合并等。这在元编程中非常有用,可以用来实现复杂的编译时逻辑。
1. 类型列表的基础:Variadic Templates
Variadic Templates 是 C++11 引入的一个强大特性,它允许函数或类模板接受任意数量的模板参数。这使得我们可以定义一个可以容纳任意数量类型的类型列表。
首先,我们定义一个空的类型列表:
template<typename...>
struct TypeList {};
template<typename Head, typename... Tail>
struct TypeList<Head, Tail...> {
using HeadType = Head;
using TailType = TypeList<Tail...>;
};
这里,TypeList 是一个类模板,它可以接受任意数量的类型参数。 第一个定义,template<typename...> struct TypeList {}; 是一个空列表的基础情况,当没有类型参数传入时,就使用这个定义。 第二个定义 template<typename Head, typename... Tail> struct TypeList<Head, Tail...> { ... }; 使用了模板参数包 Tail...。Head 表示类型列表的第一个类型,Tail... 表示类型列表中剩余的类型。 HeadType 是 Head 的别名,TailType 是剩余类型的 TypeList 的别名。
我们可以这样使用它:
using MyList = TypeList<int, double, std::string>;
MyList 现在就是一个包含 int、double 和 std::string 三个类型的类型列表。
2. 获取类型列表的长度
我们需要一个方法来确定类型列表中包含多少个类型。这可以通过递归模板来实现。
template<typename T>
struct Length;
template<>
struct Length<TypeList<>> {
static constexpr size_t value = 0;
};
template<typename Head, typename... Tail>
struct Length<TypeList<Head, Tail...>> {
static constexpr size_t value = 1 + Length<TypeList<Tail...>>::value;
};
Length 是一个类模板,它接受一个类型列表作为模板参数。 第一个特化版本 template<> struct Length<TypeList<>> { ... }; 处理空列表的情况,其长度为 0。 第二个特化版本 template<typename Head, typename... Tail> struct Length<TypeList<Head, Tail...>> { ... }; 处理非空列表的情况,其长度为 1 加上剩余类型列表的长度。
例如:
constexpr size_t my_list_length = Length<MyList>::value; // my_list_length = 3
3. 获取类型列表中指定位置的类型
接下来,我们需要一个方法来访问类型列表中特定位置的类型。这可以通过递归模板和 std::enable_if 来实现。
template<size_t Index, typename T>
struct TypeAt;
template<typename Head, typename... Tail>
struct TypeAt<0, TypeList<Head, Tail...>> {
using type = Head;
};
template<size_t Index, typename Head, typename... Tail>
struct TypeAt<Index, TypeList<Head, Tail...>> {
using type = typename TypeAt<Index - 1, TypeList<Tail...>>::type;
};
TypeAt 是一个类模板,它接受一个索引 Index 和一个类型列表 T 作为模板参数。 第一个特化版本 template<typename Head, typename... Tail> struct TypeAt<0, TypeList<Head, Tail...>> { ... }; 处理索引为 0 的情况,直接返回类型列表的 Head。 第二个特化版本 template<size_t Index, typename Head, typename... Tail> struct TypeAt<Index, TypeList<Head, Tail...>> { ... }; 处理索引大于 0 的情况,递归地在剩余的类型列表中查找索引为 Index - 1 的类型。
例如:
using type_at_1 = typename TypeAt<1, MyList>::type; // type_at_1 is double
4. 类型列表的筛选 (Filtering)
现在,我们来实现一个筛选类型列表的功能,只保留满足特定条件的类型。我们需要一个谓词 (Predicate),它是一个返回 bool 值的类型。
template<typename T>
struct IsInt {
static constexpr bool value = std::is_same_v<T, int>;
};
template<template<typename> typename Predicate, typename T>
struct Filter;
template<template<typename> typename Predicate>
struct Filter<Predicate, TypeList<>> {
using type = TypeList<>;
};
template<template<typename> typename Predicate, typename Head, typename... Tail>
struct Filter<Predicate, TypeList<Head, Tail...>> {
private:
using FilteredTail = typename Filter<Predicate, TypeList<Tail...>>::type;
public:
using type = std::conditional_t<
Predicate<Head>::value,
TypeList<Head, typename FilteredTail::HeadType, typename FilteredTail::TailType...>,
FilteredTail
>;
};
这里,IsInt 是一个谓词,用于判断一个类型是否是 int。 Filter 是一个类模板,它接受一个谓词 Predicate 和一个类型列表 T 作为模板参数。 第一个特化版本 template<template<typename> typename Predicate> struct Filter<Predicate, TypeList<>> { ... }; 处理空列表的情况,返回一个空的类型列表。 第二个特化版本 template<template<typename> typename Predicate, typename Head, typename... Tail> struct Filter<Predicate, TypeList<Head, Tail...>> { ... }; 处理非空列表的情况,首先递归地筛选剩余的类型列表,然后根据谓词的结果决定是否保留当前类型。 std::conditional_t 用于根据谓词的结果选择不同的类型列表。
注意:上面的 Filter 代码存在问题。它在Predicate::value 为 true 的时候,使用了错误的构造 TypeList 的方式。正确的代码如下:
template<typename T>
struct IsInt {
static constexpr bool value = std::is_same_v<T, int>;
};
template<template<typename> typename Predicate, typename T>
struct Filter;
template<template<typename> typename Predicate>
struct Filter<Predicate, TypeList<>> {
using type = TypeList<>;
};
template<template<typename> typename Predicate, typename Head, typename... Tail>
struct Filter<Predicate, TypeList<Head, Tail...>> {
private:
using FilteredTail = typename Filter<Predicate, TypeList<Tail...>>::type;
public:
using type = std::conditional_t<
Predicate<Head>::value,
TypeList<Head, Tail...>,
FilteredTail
>;
};
例如:
using IntList = typename Filter<IsInt, MyList>::type; // IntList is TypeList<int>
5. 类型列表的转换 (Mapping)
我们还需要一个方法来转换类型列表中的每个类型。例如,我们可以将类型列表中的所有类型转换为指针类型。
template<template<typename> typename Transformation, typename T>
struct Map;
template<template<typename> typename Transformation>
struct Map<Transformation, TypeList<>> {
using type = TypeList<>;
};
template<template<typename> typename Transformation, typename Head, typename... Tail>
struct Map<Transformation, TypeList<Head, Tail...>> {
private:
using TransformedHead = Transformation<Head>;
using TransformedTail = typename Map<Transformation, TypeList<Tail...>>::type;
public:
using type = TypeList<typename TransformedHead::type, typename TransformedTail::HeadType, typename TransformedTail::TailType...>;
};
这里,Transformation 是一个类模板,它接受一个类型作为模板参数,并定义一个 type 别名表示转换后的类型。 Map 是一个类模板,它接受一个转换器 Transformation 和一个类型列表 T 作为模板参数。 第一个特化版本 template<template<typename> typename Transformation> struct Map<Transformation, TypeList<>> { ... }; 处理空列表的情况,返回一个空的类型列表。 第二个特化版本 template<template<typename> typename Transformation, typename Head, typename... Tail> struct Map<Transformation, TypeList<Head, Tail...>> { ... }; 处理非空列表的情况,首先转换当前类型,然后递归地转换剩余的类型列表。
注意:上面的 Map 代码存在问题。它在构造 TypeList 的时候,使用了错误的构造方式。正确的代码如下:
template<typename T>
struct MakePointer {
using type = T*;
};
template<template<typename> typename Transformation, typename T>
struct Map;
template<template<typename> typename Transformation>
struct Map<Transformation, TypeList<>> {
using type = TypeList<>;
};
template<template<typename> typename Transformation, typename Head, typename... Tail>
struct Map<Transformation, TypeList<Head, Tail...>> {
private:
using TransformedHead = Transformation<Head>;
using TransformedTail = typename Map<Transformation, TypeList<Tail...>>::type;
public:
using type = TypeList<typename TransformedHead::type, typename TransformedTail::type...>;
};
例如:
template<typename T>
struct MakePointer {
using type = T*;
};
using PointerList = typename Map<MakePointer, MyList>::type; // PointerList is TypeList<int*, double*, std::string*>
6. 类型列表的合并 (Concatenation)
我们还可以将两个类型列表合并成一个类型列表。
template<typename List1, typename List2>
struct Concat;
template<>
struct Concat<TypeList<>, TypeList<>> {
using type = TypeList<>;
};
template<typename... Types2>
struct Concat<TypeList<>, TypeList<Types2...>> {
using type = TypeList<Types2...>;
};
template<typename... Types1>
struct Concat<TypeList<Types1...>, TypeList<>> {
using type = TypeList<Types1...>;
};
template<typename Head, typename... Tail, typename... Types2>
struct Concat<TypeList<Head, Tail...>, TypeList<Types2...>> {
private:
using ConcatenatedTail = typename Concat<TypeList<Tail...>, TypeList<Types2...>>::type;
public:
using type = TypeList<Head, typename ConcatenatedTail::HeadType, typename ConcatenatedTail::TailType...>;
};
Concat 是一个类模板,它接受两个类型列表 List1 和 List2 作为模板参数。 第一个特化版本 template<> struct Concat<TypeList<>, TypeList<>> { ... }; 处理两个空列表的情况,返回一个空的类型列表。 第二个和第三个特化版本处理一个列表为空的情况,返回另一个列表。 第四个特化版本 template<typename Head, typename... Tail, typename... Types2> struct Concat<TypeList<Head, Tail...>, TypeList<Types2...>> { ... }; 处理两个列表都不为空的情况,首先递归地合并剩余的类型列表,然后将第一个列表的第一个类型添加到结果中。
注意:上面的Concat 代码存在问题。它在构造 TypeList 的时候,使用了错误的构造方式。正确的代码如下:
template<typename List1, typename List2>
struct Concat;
template<>
struct Concat<TypeList<>, TypeList<>> {
using type = TypeList<>;
};
template<typename... Types2>
struct Concat<TypeList<>, TypeList<Types2...>> {
using type = TypeList<Types2...>;
};
template<typename... Types1>
struct Concat<TypeList<Types1...>, TypeList<>> {
using type = TypeList<Types1...>;
};
template<typename Head, typename... Tail, typename... Types2>
struct Concat<TypeList<Head, Tail...>, TypeList<Types2...>> {
private:
using ConcatenatedTail = typename Concat<TypeList<Tail...>, TypeList<Types2...>>::type;
public:
using type = TypeList<Head, typename ConcatenatedTail::type...>;
};
例如:
using List1 = TypeList<int, char>;
using List2 = TypeList<float, double>;
using CombinedList = typename Concat<List1, List2>::type; // CombinedList is TypeList<int, char, float, double>
7. 类型列表的移除重复项 (Remove Duplicates)
去除类型列表中重复的类型。
template <typename T>
struct RemoveDuplicates;
template <>
struct RemoveDuplicates<TypeList<>> {
using type = TypeList<>;
};
template <typename Head, typename... Tail>
struct RemoveDuplicates<TypeList<Head, Tail...>> {
private:
using TailWithoutDuplicates = typename RemoveDuplicates<TypeList<Tail...>>::type;
template <typename U>
struct IsSameAsHead {
static constexpr bool value = std::is_same_v<Head, U>;
};
using FilteredTail = typename Filter<IsSameAsHead, TypeList<Tail...>>::type;
public:
using type = TypeList<Head, typename TailWithoutDuplicates::type...>;
};
注意:上面的RemoveDuplicates 代码存在问题。应该先判断 Head 是否已经存在于 Tail 中,如果存在,则不添加 Head。正确的代码如下:
template <typename T>
struct RemoveDuplicates;
template <>
struct RemoveDuplicates<TypeList<>> {
using type = TypeList<>;
};
template <typename Head, typename... Tail>
struct RemoveDuplicates<TypeList<Head, Tail...>> {
private:
using TailWithoutDuplicates = typename RemoveDuplicates<TypeList<Tail...>>::type;
template <typename T, typename List>
struct Contains;
template <typename T>
struct Contains<T, TypeList<>> : std::false_type {};
template <typename T, typename Head2, typename... Tail2>
struct Contains<T, TypeList<Head2, Tail2...>> : std::conditional_t<std::is_same_v<T, Head2>, std::true_type, Contains<T, TypeList<Tail2...>>> {};
public:
using type = std::conditional_t<Contains<Head, TypeList<Tail...>>::value, typename RemoveDuplicates<TypeList<Tail...>>::type, TypeList<Head, typename RemoveDuplicates<TypeList<Tail...>>::type...>>;
};
例如:
using ListWithDuplicates = TypeList<int, char, int, double, char>;
using ListWithoutDuplicates = typename RemoveDuplicates<ListWithDuplicates>::type; // ListWithoutDuplicates is TypeList<int, char, double>
8. 类型列表的反转 (Reverse)
反转类型列表中类型的顺序。
template <typename T>
struct Reverse;
template <>
struct Reverse<TypeList<>> {
using type = TypeList<>;
};
template <typename Head, typename... Tail>
struct Reverse<TypeList<Head, Tail...>> {
private:
using ReversedTail = typename Reverse<TypeList<Tail...>>::type;
using ConcatenatedList = typename Concat<ReversedTail, TypeList<Head>>::type;
public:
using type = ConcatenatedList;
};
例如:
using MyList = TypeList<int, double, std::string>;
using ReversedList = typename Reverse<MyList>::type; // ReversedList is TypeList<std::string, double, int>
9. 使用场景
类型列表在元编程中有广泛的应用,例如:
- 静态多态 (Static Polymorphism): 可以根据类型列表中的类型生成不同的代码。
- 编译时配置 (Compile-time Configuration): 可以根据类型列表中的类型配置程序的行为。
- 自动代码生成 (Automatic Code Generation): 可以根据类型列表中的类型生成代码。
- 泛型编程 (Generic Programming): 用于实现更灵活和通用的代码。
10. 完整代码示例
#include <iostream>
#include <string>
#include <type_traits>
// 1. TypeList Definition
template<typename...>
struct TypeList {};
template<typename Head, typename... Tail>
struct TypeList<Head, Tail...> {
using HeadType = Head;
using TailType = TypeList<Tail...>;
};
// 2. Length
template<typename T>
struct Length;
template<>
struct Length<TypeList<>> {
static constexpr size_t value = 0;
};
template<typename Head, typename... Tail>
struct Length<TypeList<Head, Tail...>> {
static constexpr size_t value = 1 + Length<TypeList<Tail...>>::value;
};
// 3. TypeAt
template<size_t Index, typename T>
struct TypeAt;
template<typename Head, typename... Tail>
struct TypeAt<0, TypeList<Head, Tail...>> {
using type = Head;
};
template<size_t Index, typename Head, typename... Tail>
struct TypeAt<Index, TypeList<Head, Tail...>> {
using type = typename TypeAt<Index - 1, TypeList<Tail...>>::type;
};
// 4. Filter
template<typename T>
struct IsInt {
static constexpr bool value = std::is_same_v<T, int>;
};
template<template<typename> typename Predicate, typename T>
struct Filter;
template<template<typename> typename Predicate>
struct Filter<Predicate, TypeList<>> {
using type = TypeList<>;
};
template<template<typename> typename Predicate, typename Head, typename... Tail>
struct Filter<Predicate, TypeList<Head, Tail...>> {
private:
using FilteredTail = typename Filter<Predicate, TypeList<Tail...>>::type;
public:
using type = std::conditional_t<
Predicate<Head>::value,
TypeList<Head, typename FilteredTail::type...>,
FilteredTail
>;
};
// 5. Map
template<typename T>
struct MakePointer {
using type = T*;
};
template<template<typename> typename Transformation, typename T>
struct Map;
template<template<typename> typename Transformation>
struct Map<Transformation, TypeList<>> {
using type = TypeList<>;
};
template<template<typename> typename Transformation, typename Head, typename... Tail>
struct Map<Transformation, TypeList<Head, Tail...>> {
private:
using TransformedHead = Transformation<Head>;
using TransformedTail = typename Map<Transformation, TypeList<Tail...>>::type;
public:
using type = TypeList<typename TransformedHead::type, typename TransformedTail::type...>;
};
// 6. Concat
template<typename List1, typename List2>
struct Concat;
template<>
struct Concat<TypeList<>, TypeList<>> {
using type = TypeList<>;
};
template<typename... Types2>
struct Concat<TypeList<>, TypeList<Types2...>> {
using type = TypeList<Types2...>;
};
template<typename... Types1>
struct Concat<TypeList<Types1...>, TypeList<>> {
using type = TypeList<Types1...>;
};
template<typename Head, typename... Tail, typename... Types2>
struct Concat<TypeList<Head, Tail...>, TypeList<Types2...>> {
private:
using ConcatenatedTail = typename Concat<TypeList<Tail...>, TypeList<Types2...>>::type;
public:
using type = TypeList<Head, typename ConcatenatedTail::type...>;
};
// 7. Remove Duplicates
template <typename T>
struct RemoveDuplicates;
template <>
struct RemoveDuplicates<TypeList<>> {
using type = TypeList<>;
};
template <typename Head, typename... Tail>
struct RemoveDuplicates<TypeList<Head, Tail...>> {
private:
using TailWithoutDuplicates = typename RemoveDuplicates<TypeList<Tail...>>::type;
template <typename T, typename List>
struct Contains;
template <typename T>
struct Contains<T, TypeList<>> : std::false_type {};
template <typename T, typename Head2, typename... Tail2>
struct Contains<T, TypeList<Head2, Tail2...>> : std::conditional_t<std::is_same_v<T, Head2>, std::true_type, Contains<T, TypeList<Tail2...>>> {};
public:
using type = std::conditional_t<Contains<Head, TypeList<Tail...>>::value, typename RemoveDuplicates<TypeList<Tail...>>::type, TypeList<Head, typename RemoveDuplicates<TypeList<Tail...>>::type...>>;
};
// 8. Reverse
template <typename T>
struct Reverse;
template <>
struct Reverse<TypeList<>> {
using type = TypeList<>;
};
template <typename Head, typename... Tail>
struct Reverse<TypeList<Head, Tail...>> {
private:
using ReversedTail = typename Reverse<TypeList<Tail...>>::type;
using ConcatenatedList = typename Concat<ReversedTail, TypeList<Head>>::type;
public:
using type = ConcatenatedList;
};
int main() {
using MyList = TypeList<int, double, std::string, int>;
// Length
constexpr size_t my_list_length = Length<MyList>::value;
std::cout << "Length: " << my_list_length << std::endl; // Output: Length: 4
// TypeAt
using type_at_1 = typename TypeAt<1, MyList>::type;
std::cout << "Type at index 1: " << typeid(type_at_1).name() << std::endl; // Output: Type at index 1: double
// Filter
using IntList = typename Filter<IsInt, MyList>::type;
std::cout << "Filtered list: " << typeid(IntList).name() << std::endl; // Output: Filtered list: struct TypeList<int,int>
// Map
using PointerList = typename Map<MakePointer, MyList>::type;
std::cout << "Mapped list: " << typeid(PointerList).name() << std::endl; // Output: Mapped list: struct TypeList<int *,double *,char const * *,int *>
// Concat
using List1 = TypeList<int, char>;
using List2 = TypeList<float, double>;
using CombinedList = typename Concat<List1, List2>::type;
std::cout << "Combined list: " << typeid(CombinedList).name() << std::endl; // Output: Combined list: struct TypeList<int,char,float,double>
// Remove Duplicates
using ListWithDuplicates = TypeList<int, char, int, double, char>;
using ListWithoutDuplicates = typename RemoveDuplicates<ListWithDuplicates>::type;
std::cout << "List without duplicates: " << typeid(ListWithoutDuplicates).name() << std::endl; // Output: List without duplicates: struct TypeList<int,char,double>
// Reverse
using ReversedList = typename Reverse<MyList>::type;
std::cout << "Reversed list: " << typeid(ReversedList).name() << std::endl; // Output: Reversed list: struct TypeList<int,std::string,double,int>
return 0;
}
总结:构建更强大的元编程工具
通过 Variadic Templates,我们能够创建自定义的类型列表,并实现了一系列类型操作,如获取长度、访问特定位置的类型、筛选、转换、合并和移除重复项。这些操作为我们提供了强大的元编程工具,可以用于实现更复杂和灵活的编译时逻辑。
类型列表的优势和局限性
类型列表是一个强大的元编程工具,它允许我们在编译时操作类型。然而,它也有一些局限性。例如,类型列表的操作通常比较复杂,并且容易出错。此外,类型列表的编译时开销也比较大。
更高级的应用场景
类型列表可以用于实现更高级的元编程技术,例如:
- 类型推导 (Type Deduction): 可以根据类型列表中的类型推导出其他类型。
- 代码生成 (Code Generation): 可以根据类型列表中的类型生成代码。
- 编译时反射 (Compile-time Reflection): 可以获取类型列表中的类型的元信息。
希望今天的讲解能够帮助大家更好地理解 C++ 中的类型列表和 Variadic Templates。感谢大家的参与!
更多IT精英技术系列讲座,到智猿学院