别名模板

在类型定义和类型别名中,我们讨论了类型别名如何让我们为现有类型定义一个别名。

为一个所有模板参数都已明确指定的类模板创建类型别名,与创建普通类型别名的方式相同:

#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 << '
';
}

这与我们使用PairPair<T>而不是CoordCoord<T>没有任何区别。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"