缩写函数模板(abbreviated function templates)¶
| 标准 | 难度 | 状态 | 分级 | 证据 | 审阅 | 最后校验 |
|---|---|---|---|---|---|---|
| C++20 | 2/5 | final | A | 3 条(manual 3) | 单审(zyuhao7) | 2026-10-26 |
编译器支持:GCC 10+ · Clang 10+ · MSVC 19.29+。版本查询:cppreference 编译器支持。
主张与证据¶
| 主张 | 支持证据 |
|---|---|
| 在已写出元素类型的函数模板中,用 const auto& 接收任意可遍历容器 | ev-mold-abbrev-template-append-001 |
| 用单个 auto& 参数定义适用于任意容器的排序辅助函数 | ev-mold-abbrev-template-sort-002 |
| 用两个 auto 参数分别推导容器与比较器类型 | ev-mold-abbrev-template-sort-compare-003 |
本站证据页:ev-mold-abbrev-template-append-001 · ev-mold-abbrev-template-sort-002 · ev-mold-abbrev-template-sort-compare-003
解决什么问题¶
一个只依赖 begin()、end() 或比较操作的辅助函数,过去要么写完整模板头,要么为每种容器重载:
类型名只出现一次,模板参数却要单独命名。C++20 允许在函数参数位置写 auto:void sort(auto &vec)。编译器为每个 auto 参数隐式引入一个模板参数,函数仍然是函数模板,调用方式不变。
auto 前可以加概念,形成 std::integral auto、std::ranges::range auto 这类约束;这比在 template 头里写 requires 更贴近参数,也让重载选择直接受约束控制。
基础用法¶
#include <algorithm>
#include <iostream>
#include <vector>
template <typename T>
void append(std::vector<T> &x, const auto &y) {
x.insert(x.end(), y.begin(), y.end());
}
void sort(auto &vec) { std::sort(vec.begin(), vec.end()); }
void sort(auto &vec, auto less) { std::sort(vec.begin(), vec.end(), less); }
int main() {
std::vector<int> numbers{3, 1, 2};
append(numbers, std::vector{4, 0});
sort(numbers);
sort(numbers, [](int a, int b) { return a > b; });
for (int number : numbers) {
std::cout << number << '\n';
}
}
append 保留了显式的 T 来固定元素类型,const auto &y 再接受任意可遍历容器。两个 sort 各自把容器和比较器推导成独立的模板参数;调用方不需要写 <std::vector<int>>。
进阶用法¶
auto 参数可以带概念约束,把“必须是整数”或“必须是范围”写进签名。约束参与重载选择,也在不满足时给出较早的诊断。
void advance(std::integral auto &counter) { ++counter; }
template <typename T>
void append(std::vector<T> &x, const auto &y) {
x.insert(x.end(), y.begin(), y.end());
}
void sort(auto &vec) { std::sort(vec.begin(), vec.end()); }
void sort(auto &vec, auto less) { std::sort(vec.begin(), vec.end(), less); }
advance 只接受满足 std::integral 的参数;两个 sort 靠参数数量区分,容器和比较器都可以各自推导。mold 的 append 展示了混合写法:显式模板参数负责元素类型,缩写参数负责容器类型,调用点只写实际实参。
完整示例见 examples/cpp20/abbreviated_templates.cpp。
真实项目证据¶
| 项目 | 位置 | 用法 | 为什么典型 |
|---|---|---|---|
| mold | lib/common.h#L412-L415 |
显式元素类型 + const auto& 接收源容器 |
只约束必要的类型,其余交给推导,避免为每种容器写重载 |
| mold | lib/common.h#L430-L432 |
单个 auto& 参数统一排序任意容器 |
辅助函数只依赖迭代器接口,模板头没有额外信息量 |
| mold | lib/common.h#L434-L436 |
两个 auto 参数分别推导容器与比较器 |
参数各自独立推导,调用点无需显式写出比较器类型 |
三条证据均为 manual 级:作者复核,尚未引入第二名审核者。三处都在 mold 的生产公共头文件中,覆盖“混合显式参数”“单参数重载”“双参数重载”三种写法。
陷阱与边界¶
每个 auto 参数各自推导。 void f(auto a, auto b) 接受两个不同的类型;要求同类型时写成 template <typename T> void f(T a, T b),或在参数上加 std::same_as<T> 约束。
不能为隐式参数显式指定类型。 void f(auto value) 没有名字可供书写,f<int>(1) 不合法;需要显式模板实参时,保留一个显式模板参数。
花括号实参不能推导。 f({1, 2}) 无法从初始化列表推导 auto 的类型;先构造具名容器,或增加 std::initializer_list 重载。
按值 auto 会丢弃引用和顶层 cv。 void f(auto value) 复制实参;需要修改或避免拷贝时写 auto&、const auto& 或 auto&&。
缩写模板仍是函数模板。 它不能是虚函数,定义通常要放在头文件中;与显式模板版本同时存在时,两者是不同候选,调用可能产生歧义。
约束不是自动的。 不写概念的 auto 等价于无约束模板参数;需要类型能力时显式加概念,否则错误会在函数体实例化时才出现。
没有独立的特性测试宏。 标准未为本特性单列宏,通常结合 __cpp_concepts >= 202002L 与编译器版本判断,不能只看 __cplusplus。
库联动¶
- mold:
lib/common.h用缩写模板实现append与两个sort重载,调用方直接传容器和比较器。 - 与泛型 lambda 的分工:泛型 lambda 的
auto参数只服务于该 lambda;缩写函数模板生成的是可重用的命名函数,能参与普通重载与 ADL。 - 与显式模板的分工:需要显式指定类型、做部分特化或写复杂
requires子句时,完整模板头更清楚;缩写形式适合参数类型只出现一次的短辅助函数。
横向对比¶
| 方案 | 是否需要模板头 | 可显式指定类型 | 约束写法 | 主要代价 |
|---|---|---|---|---|
| 显式函数模板 | 是 | 是 | requires 子句 |
模板参数只出现一次时仍要命名 |
| 缩写函数模板 | 否 | 否 | 概念 + auto |
C++20 起可用;隐式参数无法显式指定 |
| 泛型 lambda | 否 | 否 | 概念 + auto |
只在该 lambda 内有效,不能作为独立函数复用 |
| 普通重载 | 否 | 不适用 | 无 | 每种类型重复实现,维护成本高 |
跨标准演进:C++20 通过 P1141R2 引入缩写函数模板,与 concepts 一同落地;C++23 没有改变推导规则,仍不能为隐式引入的模板参数显式指定实参。