模板元编程在翻译期间选择类型和值。标准类型特征库提供了常用的构建块;constexpr、if constexpr 和概念能够比递归类模板特化更直接地表达许多决策。
特征、变换与谓词 #
按照惯例,特征会公开一个嵌套的 type 或 value。以 _t 结尾的标准别名提取类型,而以 _v 结尾的变量模板提取布尔值或其他值。
cpp
#include <concepts>
#include <type_traits>
template <class T>
using storage_type = std::remove_cvref_t<T>;
template <class T>
inline constexpr bool is_plain_object_v =
std::same_as<T, std::remove_cvref_t<T>>;应有意识地使用变换来规范化接口:
std::remove_cv_t、std::remove_reference_t和std::remove_cvref_t会移除限定符,以便进行类型比较。std::decay_t还会执行数组到指针和函数到指针的调整,与按值传递参数的行为一致。std::conditional_t无需定义单独的特化即可从两种类型中选择一种。std::common_type_t按其指定的转换规则计算公共类型;它不能取代特定于领域的单位或精度策略。
std::is_integral_v、std::is_same_v 和 std::is_invocable_v 等谓词用于回答结构性问题。它们无法确立语义行为。
编译期分支 #
当分支依赖于模板实参时,应使用 if constexpr。被丢弃的依赖分支不会被实例化,因此其中可以包含所选类型不支持的操作。
cpp
template <class T>
auto measure(const T& value) {
if constexpr (std::is_integral_v<T>) {
return value;
} else {
return value.size();
}
}只有选中的 return 语句会被实例化,因此整数不需要 size() 成员。条件本身和非依赖代码仍必须是良构的。递归特化仍适用于归纳式类型计算,或必须支持旧版语言标准的代码,但不应以其掩盖直接的 constexpr 表达式。
地址工具与概念 #
即使 operator& 已被重载,std::addressof(object) 也能取得对象的实际地址。std::to_address(pointer) 取得原始指针或类指针值所表示的原始地址;它不会验证生命周期、是否为空或是否可解引用。
应使用概念来约束所需操作和结果属性,因为行为属于接口契约:
cpp
#include <concepts>
#include <cstddef>
#include <functional>
template <class T>
concept Hashable = requires (const T& value) {
{ std::hash<T>{}(value) } -> std::convertible_to<std::size_t>;
};特征仍适用于变换和选择加入的元数据。概念更适合表达客户端可以执行的操作,从而让重载决议和诊断信息反映这一意图。
评论
在 GitHub 上阅读或评论(发表评论需要登录)