外观
【01】模板基础
约 925 字大约 3 分钟
1. 函数重载
函数名相同,参数列表(类型 / 个数 / 顺序)不同。编译期根据实参类型选择最佳匹配。
void print(int x) { std::cout << "int " << x << '\n'; }
void print(double x) { std::cout << "double " << x << '\n'; }
void print(int x, int y) { std::cout << x << ',' << y << '\n'; }
int main() {
print(1); // 调用 print(int)
print(1.2); // 调用 print(double)
print(3, 4); // 调用 print(int,int)
}
2. 模板
模板是 “代码生成器”,在使用(实例化)时按实参推导出具体类型再生成函数 / 类。
2.1 函数模板
template<typename T>
T add(T a, T b) { return a + b; }
int main() {
add(1, 2); // 推导 T=int
add(1.0, 2.5); // 推导 T=double
add<int>(3, 4); // 显式指定
}
混合类型不会自动“跨类型”:
// add(1, 2.0); // 推导失败:T 不能同时是 int 和 double
可用额外模板参数:
template<typename T, typename U>
auto add2(T a, U b) -> decltype(a + b) { return a + b; }
非类型模板参数:
template<typename T, std::size_t N>
T sum(const T (&arr)[N]) {
T s{};
for (auto &v : arr) s += v;
return s;
}
2.2 类模板
template<typename T>
class Box {
public:
explicit Box(T v) : value(v) {}
T const& get() const { return value; }
private:
T value;
};
Box<int> bi(42);
Box<std::string> bs("hi");
别名模板:
template<typename T>
using Vec = std::vector<T>;
Vec<int> v{1,2,3};
2.3 类模板特化
全特化:对某个完全确定的参数版本给出独立实现。
template<typename T>
struct TypeName {
static constexpr const char* value = "unknown";
};
template<>
struct TypeName<int> {
static constexpr const char* value = "int";
};
template<>
struct TypeName<std::string> {
static constexpr const char* value = "string";
};
偏特化:只约束一部分模式。
template<typename T, typename U>
struct Pair { };
template<typename T>
struct Pair<T, T> { }; // 两个类型相同的特化
template<typename T>
struct Pair<T, int> { }; // 第二个为 int 的特化
函数模板没有 “偏特化”,可用重载 + SFINAE
/ if constexpr
实现类似效果:
template<typename T>
void foo(T v) { std::cout << "general\n"; }
template<typename T>
std::enable_if_t<std::is_integral_v<T>>
foo(T v) { std::cout << "integral\n"; }
3. 重载决议
编译器从候选集中选 “最佳匹配”,优先级大致如下(高 → 低):
- 非模板普通函数的精确匹配
- 模板函数的更特化版本(含显式特化)
- 模板函数的一般版本(经推导)
- 需要转换的匹配(用户自定义转换 / 标准转换)
示例:
void f(int);
template<typename T> void f(T);
template<> void f<double>(double);
int main() {
f(1); // 调用非模板 f(int)
f(1.0); // 有普通函数?否 → 匹配特化 f<double>
f('a'); // 无普通函数,无特化 → 模板实例化 f<char>
}
对比示例:普通函数 vs 函数模板(展示优先级)
void g(long); // 普通函数
template<typename T> void g(T); // 函数模板
int main() {
g(1L); // 1L 是 long
// 候选:
// 1) g(long) 精确匹配(非模板)
// 2) g<long>(long) 模板推导后也是精确匹配
// 规则:当多项同为精确匹配时,非模板优先 → 选 g(long)
g(1); // 1 是 int
// 候选:
// 1) g(long) 需要标准转换 int -> long(非精确)
// 2) g<int>(int) 模板推导后精确匹配
// 规则:更少/无转换优先 → 选 g<int>(int)
}
3.1 SFINAE
SFINAE(Substitution Failure Is Not An Error)替换失败不是错。此规则适用于函数模板的重载解析:当用显式指定或推断的类型替换模板参数失败时,将特化从重载集中丢弃,而不是导致编译错误。
该特性用于模板元编程。
小结:
- 模板只是延迟生成代码的机制
- 函数模板靠 “推导 + 实例化”,类模板靠 “显式指定”
- 特化是选择性替换
- 重载决议遵循 “更具体优先、少转换优先、普通函数