在类型定义和类型别名中,我们讨论了类型别名如何让我们为现有类型定义一个别名。
为一个所有模板参数都已明确指定的类模板创建类型别名,与创建普通类型别名的方式相同:
#include <iostream>
template <typename T>
struct Pair
{
T first{};
T second{};
};
template <typename T>
void print(const Pair<T>& p)
{
std::cout << p.first << ' ' << p.second << '
';
}
int main()
{
using Point = Pair<int>; // 创建普通类型别名
Point p { 1, 2 }; // 编译器将其替换为 Pair<int>
print(p);
return 0;
}
这样的别名可以定义在局部(例如在函数内部)或全局。
别名模板
在其他情况下,我们可能希望为一个模板类创建类型别名,但该别名并不包含所有模板参数的定义(而是由类型别名的使用者提供)。为此,我们可以定义一个别名模板,它是一个可以用来实例化类型别名的模板。正如类型别名不会定义新的类型一样,别名模板也不会定义新的类型。
以下是一个示例,展示它是如何工作的:
#include <iostream>
template <typename T>
struct Pair
{
T first{};
T second{};
};
// 这是我们的别名模板
// 别名模板必须在全局作用域中定义
template <typename T>
using Coord = Pair<T>; // Coord 是 Pair<T> 的别名
// 我们的打印函数模板需要知道 Coord 的模板参数 T 是一个类型模板参数
template <typename T>
void print(const Coord<T>& c)
{
std::cout << c.first << ' ' << c.second << '
';
}
int main()
{
Coord<int> p1 { 1, 2 }; // 在 C++20 之前:我们必须显式指定所有类型模板参数
Coord p2 { 1, 2 }; // 在 C++20 中,我们可以使用别名模板推导,在类模板参数推导(CTAD)适用的情况下推导模板参数
std::cout << p1.first << ' ' << p1.second << '
';
print(p2);
return 0;
}
在这个例子中,我们定义了一个名为Coord
的别名模板,它是Pair<T>
的别名,其中类型模板参数T
将由Coord
别名的使用者定义。Coord
是别名模板,而Coord<T>
是Pair<T>
的实例化类型别名。定义后,我们可以在使用Pair
的地方使用Coord
,在使用Pair<T>
的地方使用Coord<T>
。
关于这个例子,有几点需要注意。
首先,与普通类型别名(可以在代码块内定义)不同,别名模板必须在全局作用域中定义(因为所有模板都必须如此)。
其次,在C++20之前,我们在使用别名模板实例化对象时,必须显式指定模板参数。从C++20开始,我们可以使用别名模板推导,它会在别名类型适用类模板参数推导(CTAD)的情况下,从初始化器中推导模板参数的类型。
第三,由于类模板参数推导(CTAD)不适用于函数参数,因此当我们使用别名模板作为函数参数时,我们必须显式定义别名模板所使用的模板参数。换句话说,我们这样做:
template <typename T>
void print(const Coord<T>& c)
{
std::cout << c.first << ' ' << c.second << '
';
}
而不是这样:
void print(const Coord& c) // 不行,缺少模板参数
{
std::cout << c.first << ' ' << c.second << '
';
}
这与我们使用Pair
或Pair<T>
而不是Coord
或Coord<T>
没有任何区别。