【C++ 泛型编程 进阶篇】:用std::integral_constant和std::is_*系列深入理解模板元编程(一)https://developer.aliyun.com/article/1465295
2.5 什么时候需要继承 std::true_type和std::false_type 提供默认行为
模板编程中,有时候会有疑问,如何正确的选取编程手段。什么情况下默认行为需要继承它们。
查看下面例子:
template<typename Mapper> struct MapperTraits; template<typename Mapper> struct MapperTraits : std::false_type {};
当你定义一个模板如template struct MapperTraits;
而不提供任何默认实现,它实际上是一个不完整的模板。如果你尝试为一个没有特化的类型实例化它,编译器会产生一个错误,因为它找不到该模板的定义。
另一方面,如果你提供一个默认实现,如继承自std::false_type
:
template<typename Mapper> struct MapperTraits : std::false_type {};
那么,对于任何没有特化的类型,编译器都可以成功地实例化这个模板,并继承自std::false_type
。
这两种方法的主要区别在于它们如何处理未特化的类型:
- 不完整的模板:会导致编译错误。
- 提供默认实现:会成功编译,并为未特化的类型提供默认行为。
选择哪种方法取决于你的需求:
- 如果你想确保每种类型都有一个明确的特化,并且不希望有任何默认行为,那么使用不完整的模板是有意义的。
- 如果你想为那些没有特化的类型提供一个默认行为,那么提供一个默认实现是合适的。
在某些情况下,继承自std::false_type
或std::true_type
是有意义的,因为它们提供了一个编译时的布尔值,你可以使用这个值来进行编译时的条件检查。但如果你不需要这种行为,那么只提供一个默认实现就足够了。
- 当你继承自
std::false_type
或std::true_type
时,你通常是为了在编译时进行条件判断。这样,你可以使用这些类型特征来决定某些编译时的行为,例如选择特定的函数重载或模板特化。这种方法提供了一种默认行为,即使对于那些没有明确特化的类型。 - 当你选择不提供默认实现(即不完整的模板)时,你的意图是确保每个类型都有一个明确的特化。这样,如果某个类型没有特化,编译器会产生一个错误,从而强制开发者为该类型提供一个特化。
这两种方法都有其用途,选择哪种方法取决于你的具体需求和你想要的代码行为。
- 当你为模板提供一个默认实现(例如继承自
std::false_type
或其他任何默认行为),你实际上为模板提供了一个完整的定义。这意味着,对于那些没有特化的类型,这个默认实现会被使用。 - 当你只声明模板而不提供任何定义(即不完整的模板),你实际上只为模板提供了一个声明。这意味着,如果你尝试为一个没有特化的类型实例化这个模板,编译器会产生一个错误,因为它找不到该模板的定义。
所以,前者提供了一个完整的默认定义,而后者只是一个声明,没有定义,因此会导致编译错误。
三、std::is_类系列剖析 (Unveiling the std::is_ Class Series)
3.1 std::is_same: 识别相同类型 (Recognizing Identical Types with std::is_same)
在编程的过程中,我们有时候需要判断两种类型是否完全相同,这时候就需要用到std::is_same。它是一个模板类,可以帮助我们在编译时判断两种类型是否一致。那么它是如何做到的呢?我们来一起探究一下。
std::is_same的定义非常简单。一般来说,我们可以在的头文件中找到它。它是这样定义的:
template<class T, class U> struct is_same : std::false_type {};
当我们使用std::is_same::value时,如果T和U是不同的类型,那么结果就是false。但是如果T和U是同一种类型,我们期望得到的结果是true。这是如何实现的呢?
这就要借助模板特化的技术了。在模板特化中,我们可以为模板类定义特定的行为。对于std::is_same,它的特化版本如下:
template<class T> struct is_same<T, T> : std::true_type {};
这个特化版本告诉我们,当两个模板参数完全相同时,is_same返回的结果是std::true_type,也就是true。
有了这个工具,我们就可以在编译时期对类型进行判断了。例如,我们可以使用static_assert来检查类型:
static_assert(std::is_same<int, int>::value, "Types are not the same");
如果类型相同,那么这个断言就会通过。如果类型不同,那么就会在编译期间发出错误。
std::is_same不仅可以用于基础类型,也可以用于用户定义的类型。例如,我们定义了一个类A,然后我们可以使用std::is_same::value来判断A类的类型。
std::is_same在泛型编程中非常有用,特别是在模板函数的重载中。有时,我们需要根据模板参数的类型来选择不同的函数实现。这时,std::is_same就可以帮助我们做出正确的选择。
以上就是std::is_same的核心内容,通过它我们可以在编译时刻就确定两个类型是否相同,为我们的编程提供了极大的便利。
3.2 std::is_integral: 鉴别整型类型 (Identifying Integral Types with std::is_integral)
在C++中,整型类型(integral types)包括了bool类型、字符类型和整数类型。那么,如何在编译时判断一个类型是否为整型类型呢?答案就是使用std::is_integral。
std::is_integral 是一个模板类,作用是在编译期确定传入的类型参数是否为整型。它在 头文件中定义,其基础定义如下:
template< class T > struct is_integral;
为了适应所有整型类型,C++ 标准库对其进行了完全特化。完全特化是一种模板特化的技术,即为模板提供特定类型参数的特定实现。例如,int类型的特化版本如下:
template<> struct is_integral<int> : std::true_type {};
这样,当我们使用 std::is_integral::value 时,会得到 true。
实际上,std::is_integral不仅仅对 int 进行了特化,还对 bool、char、wchar_t、char16_t、char32_t,以及它们的有无符号形式、各种长度的整数类型进行了特化。
使用 std::is_integral 可以在编译时期进行类型检查,例如:
static_assert(std::is_integral<int>::value, "Not an integral type");
这行代码会在编译时检查int是否为整型,如果是,则编译可以通过;如果不是,则会报错。
std::is_integral在模板元编程中有很多应用,它可以在编译时判断模板参数是否为整型,从而对不同类型进行不同的处理。这在泛型编程和模板特化中非常有用。
下面是一个简单的示例,展示了如何使用 std::is_integral 来对整型和非整型参数进行不同处理:
template <typename T> void func(T t) { if constexpr (std::is_integral<T>::value) { std::cout << "Integral type\n"; } else { std::cout << "Non-integral type\n"; } }
以上就是 std::is_integral 的核心内容,希望能对你理解并应用它有所帮助。
3.3 std::is_pointer: 探寻指针类型 (Exploring Pointer Types with std::is_pointer)
在C++编程中,指针类型(pointer types)是非常重要的一类数据类型,而在模板元编程中,如何在编译时判断一个类型是否为指针类型呢?C++标准库提供了std::is_pointer,它帮助我们在编译期就可以得知某个类型是否为指针类型。
std::is_pointer
仅用于检查类型是否为原始指针类型,即形如T*
的类型,其中T
可以是任何非函数类型。对于智能指针类型,如std::shared_ptr
或std::unique_ptr
,std::is_pointer
将返回false
。
如果你想检查一个类型是否是某种特定的智能指针(如std::shared_ptr
),你需要写自己的类型特征或使用第三方库。这需要更复杂的模板元编程技术,例如模板特化和SFINAE(替换失败不是错误)。
然而,在日常编程中,我们通常不需要这种复杂性。如果我们需要特别处理指针或智能指针,我们通常会重载函数,或者使用模板并假设它们有某些特性(例如operator*
和operator->
)。
std::is_pointer是一个模板类,它接收一个类型参数,并提供一个布尔常量,如果提供的类型是一个指针类型,则值为true,否则值为false。其基本定义如下:
template< class T > struct is_pointer;
和std::is_same以及std::is_integral类似,std::is_pointer也利用了模板的特化技术。其特化的版本如下:
template< class T > struct is_pointer<T*> : std::true_type {};
这意味着当我们使用std::is_pointer::value时,如果T*是一个指针类型,那么结果就是true。
例如,下面的代码展示了如何使用std::is_pointer:
int main() { int* p = nullptr; std::cout << std::boolalpha << std::is_pointer<decltype(p)>::value; // 输出:true }
我们可以看到,std::is_pointer成功地在编译期判断出decltype§是一个指针类型。
std::is_pointer对于泛型编程来说非常有用,例如在模板函数的重载中。有时,我们需要根据模板参数的类型来选择不同的函数实现,这时std::is_pointer就能帮助我们做出正确的选择。
例如,下面的代码展示了如何在模板函数中使用std::is_pointer来进行不同的处理:
template <typename T> void process(T t) { if constexpr (std::is_pointer<T>::value) { std::cout << "Processing pointer type\n"; } else { std::cout << "Processing non-pointer type\n"; } }
以上就是std::is_pointer的核心内容,借助它我们可以在编译时期就判断出某个类型是否为指针类型,从而使我们的编程更加灵活和准确。
然而std::is_pointer
并不能区分指针所指向的类型是int
、char
还是某个类的类型。它只能判断一个类型是否是指针类型。如果你需要获取指针所指向的类型,你可以使用std::remove_pointer
这个模板,它可以把一个指针类型转换为它所指向的类型。
例如,如果你有一个int*
类型,你可以使用std::remove_pointer
获取int
类型:
typedef std::remove_pointer<int*>::type Type; // Type 是 int
然后,你可以使用std::is_same
来检查Type
是否为int
,char
或者某个类的类型:
bool is_int = std::is_same<Type, int>::value; // is_int 为 true bool is_char = std::is_same<Type, char>::value; // is_char 为 false
上述代码中,std::is_same::value
会在T1
和T2
类型完全相同时返回true
,否则返回false
。这样你就可以根据指针所指向的具体类型进行不同的操作了。
四、类型特性类特化 (Specializing Type Traits Classes)
4.1 为何要进行类型特性类的特化 (Why Specialize Type Traits Classes)
在我们深入到“为何要进行类型特性类的特化”(Why Specialize Type Traits Classes)的主题之前,先让我们想象一个场景。你正在为一家美食杂志担任摄影师,你需要拍摄各种各样的食物,从热狗到鱼子酱,从蔬菜沙拉到焦糖布丁。每一种食物都需要不同的照明,不同的角度,甚至不同的镜头来捕捉它们最诱人的一面。
编程世界中的情形也是如此。在C++编程中,我们经常会遇到各种各样的数据类型,每一种数据类型都有其特性和行为。如果我们想要编写适应多种数据类型的代码,就需要了解并处理每种数据类型的特性和行为。这就像我们需要知道如何拍摄每种食物一样。
这就是我们要进行类型特性类的特化(Specialize Type Traits Classes)的原因。通过特化类型特性类,我们可以为每种数据类型定义其特性和行为。这使我们能够编写更通用,更灵活的代码。
类型特性类的特化的基本含义
首先,我们需要理解类型特性类的特化(Specializing Type Traits Classes)的基本含义。在C++编程中,"特化"是一个非常重要的概念。它的核心思想是:对于一个模板,我们可以为某些特定的模板参数提供特定的实现。这就是所谓的“特化”。
特化类型特性类(Specializing Type Traits Classes)实际上就是这个概念的应用。我们可以为某些特定的类型提供特定的类型特性类。这样,我们就可以根据每种类型的特性和行为来调整我们的代码。
以std::is_pointer为例,这个类型特性类用于检测一个类型是否为指针类型。在C++标准库中,std::is_pointer的基本实现是返回false。但是,当类型T是一个指针类型时,std::is_pointer被特化为返回true。通过这种方式,我们可以为不同的类型提供不同的行为。
类型特性类特化的应用
类型特性类的特化(Specializing Type Traits Classes)在实际编程中有很多应用。一种常见的用法是在模板函数或模板类中使用它来调整行为。
假设我们有一个模板函数,它接受一个参数并打印该参数。对于大多数类型,我们可以直接使用std::cout来打印。但是,如果参数是一个指针,我们可能希望打印指针的地址,而不是指针指向的值。在这种情况下,我们就可以使用std::is_pointer来检测参数是否为指针,然后根据结果来调整我们的打印行为。
如果没有类型特性类的特化,我们可能需要为每种可能的类型写一个单独的函数,这显然是不现实的。通过使用类型特性类的特化,我们可以写出更通用,更灵活的代码。
总结一下,类型特性类的特化(Specializing Type Traits Classes)是模板元编程(Template Metaprogramming)中的一种重要技术。通过使用它,我们可以为每种数据类型定义其特性和行为,从而编写出更通用,更灵活的代码。在接下来的章节中,我们将详细介绍如何进行类型特性类的特化,并探讨其在实战中的应用。
4.2 如何特化类型特性类 (How to Specialize Type Traits Classes)
在了解了类型特性类特化的基本含义和应用后,接下来我们将详细介绍如何进行类型特性类的特化。特化类型特性类可以看作是一种定制化的过程,类似于定制一件衣服,我们需要根据自己的需求来确定衣服的颜色,样式等特性。类似地,特化类型特性类就是要根据特定类型的特性和行为,定制该类型的类型特性类。
类型特性类特化的基本语法
在C++中,特化类型特性类的基本语法如下:
template<> struct TypeName<TargetType> { // 重新定义或者添加成员 };
其中,TypeName
是类型特性类的名称,TargetType
是需要特化的类型。在大括号内部,我们可以重新定义或者添加类型特性类的成员。
例如,我们可以特化std::is_integral类,使其能够识别我们自定义的整型类型:
struct MyInt {}; template<> struct std::is_integral<MyInt> : std::true_type {};
在这个例子中,我们首先定义了一个名为MyInt的结构体,然后我们特化了std::is_integral类,使其能够识别MyInt类型为整型类型。我们做的实际上就是将std::is_integral的基类设为std::true_type,这样std::is_integral::value就会返回true。
类型特性类特化的注意事项
当我们特化类型特性类时,需要注意一些事项:
- 在C++标准库中,大多数类型特性类的基本实现都是继承自std::false_type或std::true_type。因此,当我们特化这些类型特性类时,通常会将其基类设为std::false_type或std::true_type,来指定::value的值。
- 类型特性类的特化应当尽可能地减少。不必要的特化会使代码复杂化,并可能导致错误。
- 特化类型特性类并不会改变原有类型特性类的行为,它只会为特定的类型添加或修改特性。
在接下来的章节中,我们将详细介绍类型特性类特化在实际编程中的应用。
【C++ 泛型编程 进阶篇】:用std::integral_constant和std::is_*系列深入理解模板元编程(三)https://developer.aliyun.com/article/1465299