非类型模板
非类型模板(Non-Type Template)是 C++ 中的一种模板形式,它允许你在模板中传递除了类型以外的其他值,比如整数、枚举、指针等。这些参数可以在编译时被解析,用于生成模板的实例化版本。
非类型模板参数(Non-Type Template Parameter)是在模板声明中,作为参数的一部分,而不是类型的一部分。它们可以是常量表达式,例如整数常量、枚举、指针、引用等。非类型模板参数的值必须在编译时是已知的,因为它们用于生成模板的特定实例。
模板参数分类类型形参与非类型形参。
类型形参:出现在模板参数列表中,跟在class
或者typename
之类的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
使用非类型模板参数的一个典型示例是实现一个固定大小的数组容器,其中数组的大小在编译时由模板参数指定。
例如,以下是一个使用非类型模板参数的示例:
template <typename T, int Size> class FixedSizeArray { private: T data[Size]; public: // ... }; // 实例化一个大小为 10 的 FixedSizeArray,存储 int 类型 FixedSizeArray<int, 10> intArray;
在这个示例中,int Size
就是一个非类型模板参数,它确定了 FixedSizeArray
的数组大小。在编译时,intArray
的大小将被确定为 10
,因为在实例化时传递了 10
给 Size
参数。
注意:
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
模板的特化
模板的特化(Template Specialization)是 C++
中一种允许你为特定类型或特定条件提供定制化实现的机制。模板特化允许你在针对某些特定类型或条件的情况下,提供一个专门的模板实现,以覆盖通用模板的行为。
在一般的模板中,你可能会为不同类型的数据提供一个通用的模板实现。然而,在某些情况下,特定类型可能需要特殊处理或定制化的行为。这时,你可以通过模板特化来为这些特定类型提供特殊实现。
模板特化分为两种类型:
类模板特化(Class Template Specialization):针对特定类型的特殊实现。
函数模板特化(Function Template Specialization):针对特定类型的特殊实现。
在某些情况下,如果没有使用模板的特化,可能会导致编译器选择错误的实现,从而产生不明确的错误或意外的行为。一个常见的情况是函数模板的参数匹配不明确,编译器无法确定应该使用哪个模板实现。这种情况下,特化可以提供明确的信息,帮助编译器正确选择实现。
以下是一个示例,展示在没有模板特化的情况下可能会发生的问题:
#include <iostream> template <typename T> void printType(T value) { std::cout << "Generic template" << std::endl; } // 模板特化版本,针对 int 类型 template <> void printType<int>(int value) { std::cout << "Specialized template for int" << std::endl; } int main() { int num = 5; printType(num); // 编译错误,不明确的调用 return 0; }
在上面的示例中,没有模板特化的情况下,编译器无法确定应该调用哪个模板实现,因为 printType
函数可以接受任何类型的参数。这会导致编译错误,因为编译器无法根据上下文来选择正确的实现。
通过为 printType<int>
提供特化版本,我们可以明确告诉编译器在处理 int
类型参数时应该使用哪个实现。这种情况下,特化可以解决不明确调用的问题,确保编译器选择正确的实现。
1.函数模板特化
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字
template
后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
函数模板特化的示例:
#include <iostream> // 通用的函数模板 template <typename T> T add(T a, T b) { return a + b; } // 函数模板的特化版本,针对 int 类型 template <> int add(int a, int b) { std::cout << "Specialized version for int" << std::endl; return a + b + 10; } int main() { int result1 = add(5, 3); // 调用通用版本 std::cout << "Result 1: " << result1 << std::endl; // 输出: 8 int result2 = add<int>(5, 3); // 调用特化版本 std::cout << "Result 2: " << result2 << std::endl; // 输出: Specialized version for int // Result 2: 18 return 0; }
在上面的示例中,add
是一个函数模板,用于计算两个数的和。通过为 add<int>
提供特化版本,我们覆盖了默认的行为,并在特定类型下进行了定制化的操作。在 main
函数中,我们展示了如何调用通用版本和特化版本,以及它们的输出结果。
注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。
2.类模板特化
在 C++ 中,类模板特化是指为特定类型或条件提供定制化的类模板实现。有两种类型的类模板特化:全特化(Full Specialization)和偏特化(Partial Specialization)。
全特化(Full Specialization):全特化是针对特定类型的完整特化。当你为某个特定类型提供了一个完整的特化版本,它将会覆盖通用的类模板定义。全特化的语法是在模板名后面添加 <>
并指定特化的类型。
示例:
// 通用的类模板 template <typename T> class MyTemplate { public: void print() { std::cout << "Generic Template" << std::endl; } }; // 类模板的全特化版本,针对 int 类型 template <> class MyTemplate<int> { public: void print() { std::cout << "Specialized Template for int" << std::endl; } };
偏特化(Partial Specialization):偏特化是在某些条件下对模板参数进行特化,通常用于更细粒度的定制。偏特化允许你为某些特定情况提供特化版本,而不是为每个类型提供完整的特化版本。偏特化的语法是在模板名后面添加 <>
,并在尖括号中指定要特化的参数。
示例:
// 通用的类模板 template <typename T, typename U> class Pair { public: Pair(T first, U second) : first_(first), second_(second) {} void print() { std::cout << "Generic Pair: " << first_ << ", " << second_ << std::endl; } private: T first_; U second_; }; // 类模板的偏特化版本,针对两个相同类型的参数 template <typename T> class Pair<T, T> { public: Pair(T first, T second) : first_(first), second_(second) {} void print() { std::cout << "Specialized Pair for same type: " << first_ << ", " << second_ << std::endl; } private: T first_; T second_; };
在上述示例中,全特化针对整型类型提供了特化版本,而偏特化则针对两个相同类型的参数提供了特化版本。
总结:
全特化:为特定类型提供完整特化版本。
偏特化:为特定情况或条件提供特化版本。
通过使用模板特化,你可以在需要定制化行为的情况下,为类模板提供精确的实现,增强了模板的灵活性和适用性。
类模板允许我们为不同的数据类型提供相同的代码结构,以适应多种类型的需求。然而,在某些情况下,编译器可能无法推断出如何比较不同类型的对象。这就是为什么在下面的示例中,如果不使用类模板的特化,编译器可能无法正确排序日期类的原因。
在默认情况下,std::sort
函数使用元素的<
运算符来比较元素的大小。对于基本类型(如整数)或支持 <
运算符的类型,这是没有问题的。但对于自定义类型(如日期类),编译器无法知道如何执行比较。
通过使用类模板的特化,我们为不同类型的日期类提供了明确的比较逻辑,即 operator<
运算符的重载。这告诉编译器如何在特定情况下比较日期对象,使得 std::sort
函数能够正确工作。
特化类模板允许我们在需要的地方为特定类型提供定制的实现,从而解决编译器无法推断的问题,确保程序能够正确运行。
下面是一个关于日期类的示例,使用类模板特化来比较大小,并通过 std::sort
函数对日期进行排序。
在示例中,我们定义了一个 Date
类模板,然后为 Date
类提供了特化版本,用于实现日期的比较操作:
#include <iostream> #include <vector> #include <algorithm> // 通用的类模板 template <typename T> class Date { public: Date(T year, T month, T day) : year_(year), month_(month), day_(day) {} // 比较运算符 bool operator<(const Date& other) const { if (year_ != other.year_) return year_ < other.year_; if (month_ != other.month_) return month_ < other.month_; return day_ < other.day_; } void print() { std::cout << year_ << "-" << month_ << "-" << day_ << std::endl; } private: T year_; T month_; T day_; }; // 类模板的特化版本,用于 int 类型 template <> class Date<int> { public: Date(int year, int month, int day) : year_(year), month_(month), day_(day) {} bool operator<(const Date<int>& other) const { if (year_ != other.year_) return year_ < other.year_; if (month_ != other.month_) return month_ < other.month_; return day_ < other.day_; } void print() { std::cout << year_ << "-" << month_ << "-" << day_ << std::endl; } private: int year_; int month_; int day_; }; int main() { std::vector<Date<int>> dates = { {2022, 8, 15}, {2021, 12, 25}, {2022, 1, 1}, {2022, 3, 20} }; std::cout << "Before sorting:" << std::endl; for (const auto& date : dates) { date.print(); } std::sort(dates.begin(), dates.end()); std::cout << "After sorting:" << std::endl; for (const auto& date : dates) { date.print(); } return 0; }
在上面的示例中,我们首先定义了一个通用的 Date
类模板,用于存储年、月、日信息。然后,我们为 Date<int>
提供了特化版本,用于实现基于 int
类型的日期比较操作。
在 main
函数中,我们创建了一个存储日期的 std::vector
,然后使用 std::sort
函数对日期进行排序。由于我们为 Date<int>
提供了特化版本,它能够正确比较日期并进行排序。最终,我们分别输出排序前后的日期顺序。