1 type_traits 的属性类型特性

1.1 std::alignment_of

std::alignment_of 是一个模板类,用于获取类型的对齐要求。对齐是指数据在内存中的位置,某些硬件平台可能要求特定类型的数据位于特定地址的边界上。

定义:

template< class T >  
struct alignment_of;

样例:

#include <iostream>  
#include <type_traits>  

int main() {
	std::cout << "Alignment of int: " << std::alignment_of<int>::value << std::endl;	
	std::cout << "Alignment of double: " << std::alignment_of<double>::value << std::endl;
	return 0;
}

上面代码输出为:

Alignment of int: 4
Alignment of double: 8

1.2 std::rank

std::rank 是一个模板类,用于获取数组类型的维度数。对于非数组类型,其值为 0。

定义:

template< class T >  
struct rank;

样例:

#include <iostream>  
#include <type_traits>  

int main() {
	std::cout << "Rank of int: " << std::rank<int>::value << std::endl;
	std::cout << "Rank of int[3]: " << std::rank<int[3]>::value << std::endl;
	std::cout << "Rank of int[3][4]: " << std::rank<int[3][4]>::value << std::endl;
	return 0;
}

上面代码输出为:

Rank of int: 0
Rank of int[3]: 1
Rank of int[3][4]: 2

1.3 std::extent

std::extent 是一个模板类,用于获取数组类型的长度。它通常与 std::rank 结合使用,以确定特定维度的长度。

定义:

template< class _Ty, unsigned int _Nx = 0 >  
struct extent;

样例:

#include <iostream>  
#include <type_traits>  

int main() {
	std::cout << "Rank of int: " << std::rank<int>::value << std::endl;
	std::cout << "Rank of int[3]: " << std::rank<int[3]>::value << std::endl;
	std::cout << "Rank of int[3][4]: " << std::rank<int[3][4]>::value << std::endl;
	return 0;
}

上面代码输出为:

Extent of int[3]: 3
First dimension extent of int[3][4]: 3
Second dimension extent of int[3][4]: 4

1.4 std::remove_all_extents

std::remove_all_extents 是一个模板类,用于移除数组类型的所有维度,将其转换为其元素类型。换句话说,它将多维数组转换为其基础元素类型。

定义:

template< class T >  
struct remove_all_extents;

样例:

#include <iostream>  
#include <type_traits>  
  
int main() {  
    std::cout << typeid(std::remove_all_extents<int[3][4]>::type).name() << std::endl;  
    return 0;  
}

上面代码输出为:

int

注意:typeid(T).name() 在不同的编译器和平台上可能会返回不同的字符串,因此上述输出可能会因环境而异。但核心思想是 std::remove_all_extents 将数组类型转换为其基础元素类型。

2 std::rank 与 std::extent 的综合应用示例

std::rank 和 std::extent 分别用于获取数组或指针类型的维度数和特定维度的长度。这两个工具在泛型编程和模板元编程中特别有用,尤其是在处理不同维度的数组或指针类型时。

下面是一个综合使用 std::rank 和 std::extent 的真实应用场景示例:一个函数模板,用于打印任意维度数组的形状(即各维度的长度)。

#include <iostream>  
#include <type_traits>  

// 递归辅助函数模板,用于打印数组形状  
template <typename T, std::size_t N = std::rank<T>::value>
struct print_array_shape_helper;

// 特化:数组维度为0时,结束递归  
template <typename T>
struct print_array_shape_helper<T, 0> {
	static void print(const T&) {
		// 无需打印,因为没有更多维度  
	}
};

// 递归情况:打印当前维度大小,并递归调用下一个维度  
template <typename T, std::size_t N>
struct print_array_shape_helper {
	static void print(const T& arr) {
		std::cout << std::extent<T, N - 1>::value << " ";
		print_array_shape_helper<T, N - 1>::print(arr);
	}
};

// 打印数组形状的包装函数  
template <typename T>
void print_array_shape(const T& arr) {
	constexpr std::size_t rank = std::rank<T>::value;
	if (rank > 0) {
		std::cout << "Array shape: ";
		print_array_shape_helper<T, rank>::print(arr);
		std::cout << std::endl;
	}
	else {
		std::cout << "Not an array type." << std::endl;
	}
}

int main() 
{
	// 一维数组  
	int arr1[] = { 1, 2, 3, 4, 5 };
	print_array_shape(arr1); // 输出: Array shape: 5  

	// 二维数组  
	int arr2[][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
	print_array_shape(arr2); // 输出: Array shape: 3 3  

	// 不是数组的类型  
	int non_array = 42;
	print_array_shape(non_array); // 输出: Not an array type.  

	return 0;
}

上面代码的输出为:

Array shape: 5
Array shape: 3 3
Not an array type.

在这个示例中,print_array_shape 函数模板接受一个数组作为参数,并使用 std::rank 来确定其维度数(即秩)。然后,它使用 std::make_index_sequence 生成一个从 0 到 rank - 1 的索引序列,并将其传递给 print_array_shape_impl 函数模板。

print_array_shape_impl 函数模板接受一个数组和一个索引序列作为参数。它使用折叠表达式(C++17及更高版本中的特性)和 std::extent 来遍历数组的每一个维度,并打印出每一维的长度。

通过这种方式,我们可以编写一个通用的函数来打印任意维度数组的形状,而无需事先知道数组的确切维度数。这种技术对于处理不同大小和维度的数组非常有用,尤其是在科学计算、数据分析或任何需要处理多维数据的领域中。

3 type_traits 的操作类型特性

3.1 std::conditional

std::conditional 是一个模板类,它接受一个布尔条件、两个类型 T 和 F 作为模板参数,然后根据条件在 T 和 F 之间选择一个类型。如果条件为 true,则选择 T,否则选择 F。

定义:

template<class _Ty1,
	class _Ty2>
struct conditional<true, _Ty1, _Ty2>

样例:

#include <iostream>  
#include <type_traits>  

int main() {
	using Type1 = std::conditional<true, int, double>::type; // Type1 是 int  
	using Type2 = std::conditional<false, int, double>::type; // Type2 是 double  

	std::cout << typeid(Type1).name() << std::endl; 
	std::cout << typeid(Type2).name() << std::endl; 

	return 0;
}

上面代码输出为:

int
double

3.2 std::common_type

std::common_type 是一个模板类,它接受多个类型作为模板参数,并确定这些类型的公共类型。如果所有类型都可以隐式转换为某个类型,那么 common_type 就是该类型。

定义:

template<class _Ty1,
	class _Ty2,
	class... _Rest>
struct common_type<_Ty1, _Ty2, _Rest...> : _Common_type3<void, _Ty1, _Ty2, _Rest...>

样例:

#include <iostream>  
#include <type_traits>  
  
int main() {  
    using CommonType = std::common_type<int, float, double>::type; // CommonType 是 double  
  
    std::cout << typeid(CommonType).name() << std::endl; 
  
    return 0;  
}

上面代码输出为:

double

3.3 std::decay

std::decay 是一个模板类,它接受一个类型作为模板参数,并产生一个“衰减”后的类型。这通常意味着它会去除引用和 CV 限定符(const 和 volatile),如果是数组或函数类型,则会转换为对应的指针类型。

定义:

template<class T>
struct decay

样例:

#include <iostream>  
#include <type_traits>  

int main() {
	using DecayedType = std::decay<int&>::type; // DecayedType 是 int  
	using DecayedArrayType = std::decay<int[5]>::type; // DecayedArrayType 是 int*  

	std::cout << typeid(DecayedType).name() << std::endl; 
	std::cout << typeid(DecayedArrayType).name() << std::endl; 

	return 0;
}

上面代码输出为:

int
int * __ptr64

3.4 std::enable_if

std::enable_if 是一个模板类,它根据一个布尔条件来启用或禁用模板特化。它通常与 std::is_same、std::is_integral 等类型特性检查模板结合使用,以在编译时根据类型特性进行条件编译。

定义:

template<bool _Test,
	class _Ty = void>
struct enable_if

样例:

#include <iostream>  
#include <type_traits>  

template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void foo(T) {
	std::cout << "Integral type." << std::endl;
}

template<typename T, typename std::enable_if<!std::is_integral<T>::value, int>::type = 0>
void foo(T) {
	std::cout << "Non-integral type." << std::endl;
}

int main() {
	foo(12);  
	foo(3.14); 

	return 0;
}

上面代码输出为:

Integral type.
Non-integral type.

4 std::conditional 应用于类型包装器

下面是一个使用 std::conditional 的示例,展示了如何在模板中基于类型属性选择一个类类型或者类型别名:

#include <iostream>  
#include <type_traits>  

// 整数类型包装器  
template<typename T>
struct IntegerWrapper {
	T value;
	IntegerWrapper(T v) : value(v) {}
	void print() const {
		std::cout << "Integer: " << value << std::endl;
	}
};

// 浮点类型包装器  
template<typename T>
struct FloatWrapper {
	T value;
	FloatWrapper(T v) : value(v) {}
	void print() const {
		std::cout << "Float: " << value << std::endl;
	}
};

// 使用std::conditional根据类型属性选择包装器  
template<typename T>
using WrapperType = typename std::conditional<
	std::is_integral<T>::value,
	IntegerWrapper<T>,
	FloatWrapper<T>
>::type;

int main() {
	// 使用WrapperType为整数类型选择包装器  
	WrapperType<int> intWrapper(12);
	intWrapper.print(); // 输出 "Integer: 12"  

	// 使用WrapperType为浮点类型选择包装器  
	WrapperType<double> floatWrapper(3.14);
	floatWrapper.print(); // 输出 "Float: 3.14"  

	return 0;
}

上面代码输出为:

Integer: 12
Float: 3.14

这个示例定义了两个模板包装器:IntegerWrapper 用于整数类型,FloatWrapper 用于浮点类型。然后,使用 std::conditional 来根据类型 T 是否为整数类型来选择相应的包装器。这里 std::conditional 有三个模板参数:第一个是一个布尔表达式,第二个是在表达式为 true 时选择的类型,第三个是在表达式为 false 时选择的类型。

WrapperType 是一个类型别名模板,它使用了 std::conditional 来选择适当的包装器类型。然后,在 main 函数中,分别创建了一个整数类型的包装器和一个浮点类型的包装器,并调用了它们的 print 方法。

5 std::enable_if 应用于类型安全的加法函数

下面示例是一个简单的类型安全的加法函数,它根据输入类型选择不同的加法实现:

#include <iostream>  
#include <type_traits>  

// 整数加法  
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
add(T a, T b) {
	return a + b;
}

// 浮点加法  
template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
add(T a, T b) {
	return a + b;
}

// 通用加法包装器 
template<typename T>
auto add_wrapper(T a, T b) -> decltype(add(a, b)) {
	return add(a, b);
}

template<typename T, bool IsIntegral>
struct adder;

template<typename T>
struct adder<T, true> {
	static T add(T a, T b) {
		return a + b; // 整数加法实现  
	}
};

template<typename T>
struct adder<T, false> {
	static T add(T a, T b) {
		return a + b; // 浮点加法实现(假设T是浮点类型)  
	}
};

// 使用std::conditional来选择加法器实现  
template<typename T>
T conditional_add(T a, T b) {
	return adder<T, std::is_integral<T>::value>::add(a, b);
}

int main() 
{
	// 使用add_wrapper进行整数加法  
	std::cout << "5 + 3 (integers) = " << add_wrapper(5, 3) << std::endl;

	// 使用add_wrapper进行浮点加法  
	std::cout << "5.0 + 3.0 (floats) = " << add_wrapper(5.0, 3.0) << std::endl;

	// 使用conditional_add进行整数加法  
	std::cout << "5 + 3 (integers via conditional) = " << conditional_add(5, 3) << std::endl;

	// 使用conditional_add进行浮点加法  
	std::cout << "5.0 + 3.0 (floats via conditional) = " << conditional_add(5.0, 3.0) << std::endl;

	return 0;
}

上面代码输出为:

5 + 3 (integers) = 8
5.0 + 3.0 (floats) = 8
5 + 3 (integers via conditional) = 8
5.0 + 3.0 (floats via conditional) = 8

这个示例定义了两个重载的 add 函数模板,分别用于整数和浮点数的加法。然后创建了一个 add_wrapper 函数模板,它使用 decltype 和 enable_if 来根据参数类型选择正确的 add 函数。

这种基于条件的类型选择在模板元编程中非常常见,允许程序员编写更加通用和灵活的代码,同时保持类型安全。

03-24 11:13