模板是一个由类型、常量值、模板和参数包参数化的声明族。特化是在 C++ 名称查找、类型检查、重载决议和约束规则下形成的;模板不是文本替换。
模板参数 #
类型参数接受类型,常量模板参数接受结构化常量值,模板模板参数接受兼容的模板。默认值用于提供省略的实参。参数包表示零个或多个参数或实参。
cpp
template <class T, std::size_t N = 16>
struct Buffer;
template <auto Value>
struct Constant;
template <template <class...> class Container, class T>
using one_element_container = Container<T>;
template <class... Ts>
struct TypeList {};类模板或别名模板可以具有类型参数、常量参数、模板参数和参数包。函数模板还支持推导:出现在可推导参数位置的实参会从调用实参中推断,而不可推导参数必须由调用方提供,或从其他位置获得。
cpp
template <class T>
T maximum(T left, T right);
auto a = maximum(1, 2); // T is int
auto b = maximum<double>(1, 2.5);显式模板实参位于普通调用实参之前。推导不会使用函数返回类型,因为调用结果可能被丢弃,而且重载决议需要在结果类型已知之前获得候选类型。
模板类别和 CTAD #
函数模板、类模板、变量模板、别名模板和成员模板具有不同的实例化规则。
cpp
#include <type_traits>
template <class T> T identity(T value) { return value; } // function
template <class T> struct Box { T value; }; // class
template <class T> inline constexpr bool is_box = false; // variable
template <class T> using remove_cvref_t = // alias
std::remove_cvref_t<T>;
struct Store {
template <class T> void put(T&& value); // member
};类模板实参推导(CTAD)根据构造函数、聚合推导候选项或用户定义的推导指引来选择类模板实参。当使用不带显式实参的类模板名称时,CTAD 会生效;它不适用于函数模板。
cpp
template <class T>
struct Pair { T first; T second; };
template <class T, class U>
Pair(T, U) -> Pair<std::common_type_t<T, U>>;
Pair values{1, 2}; // Pair<int> through the deduction guide
当构造函数参数类型无法表达预期的存储类型时,应编写推导指引。如果明确写出类型能够更清晰地表达所有权、单位或窄化策略,则不要在 API 边界依赖 CTAD。
实例化和可见性 #
当需要某个特化时,会发生隐式实例化,但须遵守实例化点规则。相关模板定义必须在该点可达,这也是函数和类模板定义通常位于头文件中的原因。
显式实例化定义会生成一个特化;extern template声明会抑制翻译单元中的隐式实例化,而该翻译单元可以使用其他位置提供的实例化。
cpp
// widget.hpp
template <class T>
class Widget {
public:
void draw();
};
extern template class Widget<int>;
// widget.cc, after the definition is visible
template <class T>
void Widget<T>::draw() {}
template class Widget<int>;显式实例化是一种构建时控制手段,不能替代使每个所需特化都有效。确保声明在其使用之前可见,并在编译器必须进行实例化的所有位置保持模板定义可见。
评论
在 GitHub 上阅读或评论(发表评论需要登录)