C++ 萃取技术——值萃取
在 C++ 编程中,萃取技术(Traits)是模板元编程的一个核心概念,用于在编译时提供类型相关的信息。其中,值萃取(Value Traits)是一种特定形式的萃取技术,它允许程序员为不同的数据类型定义特定的属性或值。
值萃取本质上是一个模板结构体或者模板类,它为给定的类型提供编译时可计算的值或类型。其中包含一个静态常量表达式(通常是
static_constexpr
成员),这个常量表达式的值基于模板参数进行计算。值萃取可以用来确定某些类型属性(如是否是整型、大小等),或者根据类型计算某些特定的值(如阶乘、斐波那契数等)。
1. 常规范例
首先定义一个简单的类 A
,包含一个整数成员变量,并重载 +=
操作符以支持累加操作。
#include <iostream>
class A {
public:
A(int v1, int v2) : m_i(v1 + v2) {}
int m_i;
A& operator +=(const A& obj)
{
m_i += obj.m_i;
return *this;
};
};
随后,定义模板结构 SumFixedTraitraits
用于固定萃取。这个结构为不同的数据类型定义了适当的求和类型和初始值。
template<typename T>
struct SumFixedTraitraits; // 基础模板声明,无需实现
// 特化版本为 char 类型
template<>
struct SumFixedTraitraits<char>
{
using sumT = int;
static sumT initValue()
{
return 0;
}
};
// 特化版本为 int 类型
template<>
struct SumFixedTraitraits<int>
{
using sumT = __int64;
static sumT initValue()
{
return 0;
}
};
// 特化版本为 double 类型
template<>
struct SumFixedTraitraits<double>
{
using sumT = double;
static sumT initValue()
{
return 0.0;
}
};
// 特化版本为类 A 类型
template<>
struct SumFixedTraitraits<A>
{
using sumT = A;
static sumT initValue()
{
return sumT{0, 0};
}
};
一个泛型函数 funcsum
利用 SumFixedTraitraits
来确定累加器的类型和初始值,并计算从 begin
到 end
的数组元素之和。
template<typename T>
auto funcsum(const T* begin, const T* end)
{
// 传入一个类型(T),返回一个类型(sumT),这是固定萃取的运用
using sumT = typename SumFixedTraitraits<T>::sumT;
sumT sum = SumFixedTraitraits<T>::initValue();
for(;;)
{
sum += (*begin);
if(begin == end)
{
break;
}
++begin;
}
return sum;
}
在 main
函数中,对各种类型的数组使用 funcsum
并输出结果。
int main()
{
int myintarray1[] = {10, 15, 20};
int myintarray2[] = {1000000000, 1500000000, 2000000000};
char mychararray[] = "abc";
double mydblarray1[] = {12.8, 15.8, 20.6};
A myaobjarray1[] = {A{2, 3}, A{6, 8}, A{11, 12}};
std::cout << funcsum(myintarray1, myintarray1 + 3) << std::endl;
std::cout << funcsum(myintarray2, myintarray2 + 3) << std::endl;
std::cout << static_cast<int>(funcsum(mychararray, mychararray + 3)) << std::endl;
std::cout << funcsum(mydblarray1, mydblarray1 + 3) << std::endl;
std::cout << funcsum(myaobjarray1, myaobjarray1 + 3).m_i << std::endl;
return 0;
}
在上述示例中,值萃取技术的应用非常显著。通过定义 SumFixedTraitraits
模板结构,可以为不同的数据类型特定化适合的求和类型和初始值,展示了如何利用模板特化来适应不同数据类型的需求。
解析常规范例中的值萃取应用
- 类型特化:
- 在
SumFixedTraitraits
结构中,对于每个数据类型(如char
,int
,double
,A
类),定义了一个特化版本。这些特化版本分别定义了一个sumT
类型和一个initValue
静态方法。sumT
确定了用于累计求和的数据类型,而initValue
提供了该数据类型的初始值。
- 在
- 类型安全与初值设定:
- 通过为每个类型提供一个明确的初始值,例如对于
char
类型是0
(整数),对于double
是0.0
,这样的设计确保了在进行求和操作时,累加器的初始状态是正确的,且符合数据类型的性质。这也避免了任何隐式类型转换,增加了代码的类型安全性。
- 通过为每个类型提供一个明确的初始值,例如对于
- 泛型函数的使用:
funcsum
函数展示了如何使用值萃取。它首先通过typename SumFixedTraitraits<T>::sumT
获取适当的累加器类型。然后使用SumFixedTraitraits<T>::initValue()
获取累加器的初始值。这种方式使得funcsum
函数能够适用于任何定义了相应特化的数据类型。
- 扩展性和灵活性:
- 如果需要支持新的数据类型,只需为该类型添加
SumFixedTraitraits
的特化版本即可。这种设计使得原有代码易于扩展且不需要修改,只需增加新的特化定义。
- 如果需要支持新的数据类型,只需为该类型添加
2. 判断是否为 void 类型的范例
C++标准库中有一个类模板is_void,用来判断某个类型是不是void类型。在main()主函数中简单写一段测试代码:
int main()
{
std::cout << "int是void类型吗?" << std::is_void<int>::value << std::endl; // 0
std::cout << "void是void类型吗?" << std::is_void<void>::value << std::endl; // 1
return 0;
}
那么,这个is_void是怎么实现的呢?其实,is_void就可以看作一个值萃取类模板,这里也写一个值萃取类模板实现is_void类似的功能。
请不要忘记,值萃取类模板的功能是“传入一种类型,萃取出一个值”。
下面范例中,展示了如何使用值萃取类模板来判断一个类型是否为 void
类型。这是 is_void
的简化实现,用以说明值萃取技术在类型判断中的应用。
#include <iostream>
template <typename T>
struct voidValueTraits
{
static const bool value = false;
};
template<> //特化版本
struct voidValueTraits<void>
{
static const bool value = true;
};
int main()
{
std::cout << "int是void类型吗?" << voidValueTraits<int>::value << std::endl; // 0
std::cout << "void是void类型吗?" << voidValueTraits<void>::value << std::endl; // 1
return 0;
}
使用场景和优点
- 类型判断:通过这种方法,可以在编译时判断任何类型是否为
void
,这对于一些模板函数或类在处理特定类型时非常有用,比如在模板元编程中做条件编译或者在运行时避免某些行为。 - 编译时计算:所有的检查都在编译时完成,没有运行时成本。
- 易于扩展:虽然这里只针对
void
类型进行特化,但这种模式可以被用来检测和萃取任何类型的属性,例如检测是否是指针类型、是否是整数类型等。
3. 判断两个类型是否相等
在 C++ 的标准库中,std::is_same
类模板用于判断两个类型是否相同。类似地,可以利用值萃取技术编写一个自定义的萃取类模板来实现这一功能。以下是如何通过一个简单的值萃取类模板达到与 std::is_same
相似的效果。
首先,创建一个基本模板和一个特化模板:
#include <iostream>
//泛化版本
template<typename T1, typename T2>
struct IsSameType
{
static const bool value = false;
};
//特化版本
template<typename T1>
struct IsSameType<T1,T1>
{
static const bool value = true;
};
int main()
{
std::cout << IsSameType<int, const int>::value << std::endl; // 0
std::cout << IsSameType<int, int>::value << std::endl; // 1
return 0;
}
可以用一个变量模板(不能用别名模板,因为结果是一个值,不是一个类型)简化书写,代码如下。
template<typename T1,typename T2>
const bool IsSame_v = IsSameType<T1,T2>::value;
int main()
{
std::cout << IsSame_v<int, const int> << std::endl; // 0
std::cout << IsSame_v<int, int> << std::endl; // 1
return 0;
}
通过继承自 std::true_type
和 std::false_type
,可以进一步简化模板的定义。这两个类型已经内置了一个 value
静态成员,分别代表布尔值 true
和 false
。
#include <iostream>
template<typename T1, typename T2>
struct IsSameType : std::false_type {};
template<typename T1>
struct IsSameType<T1, T1> : std::true_type {};
template<typename T1,typename T2>
const bool IsSame_v = IsSameType<T1,T2>::value;
int main()
{
std::cout << IsSame_v<int, const int> << std::endl; // 0
std::cout << IsSame_v<int, int> << std::endl; // 1
return 0;
}
从上面的代码中可以看到,类模板IsSameType
是空的,不需要有任何代码行,只需要继承std::false_type
和std::true_type
表达一个“假”或“真”的含义就行了,std::false_type
和std::true_type
类模板中本身就定义了value静态成员。
解析 IsSameType 的实现
- 基本及特化模板定义:
- 泛化版本的
IsSameType<T1, T2>
默认将value
设置为false
,表示两个类型不相同。 - 特化版本的
IsSameType<T1, T1>
将value
设置为true
,只有当两个模板参数相同(即T1
与T1
),才会触发这个特化版本,表示两个类型相同。
- 泛化版本的
- 变量模板简化:
IsSame_v<T1, T2>
是一个变量模板,它直接提供了一种更简洁的方式来获取两个类型是否相同的结果。这种方式在代码中更加直观和易于使用。
- 使用
std::true_type
和std::false_type
:- 优化后的
IsSameType
直接继承自std::false_type
或std::true_type
。这些类型已经在标准库中定义了value
成员,分别为false
和true
。通过继承这些类型,可以省略在自己的结构体中定义value
的代码,并且能够直接利用标准库提供的功能。 - 这种继承方式还允许
IsSameType
与其他标准类型萃取类无缝集成,因为它们都遵循相同的模式。
- 优化后的
使用场景和优点
- 通用性:这种类型比较机制可以在很多场合使用,例如在编写模板代码时进行特化或者在编译时进行类型检查。
- 编译时优化:所有的类型检查都是在编译时完成的,没有运行时开销。
- 易于整合和扩展:使用标准的
true_type
和false_type
使得这个萃取技术与其他标凑库组件高度兼容,易于整合和扩展。