函数模板
函数模板不是函数,只有实例化函数模板,编译器才能生成实际的函数定义。不过在很多时候,它看起来就像普通函数一样,你可以把它当作一系列相同功能的函数的集合,编译器会在编译时生成这些函数,而不是手动为每个类型编写。
定义
template<typename T>
T max(T a,T b){
return a > b ? a : b;
}这个模板可以比较两个中谁更大,只要变量实现了operator>运算,那么都可以进行比较。
struct Test{
int v_{};
Test() = default;
Test(int v) :v_(v) {}
bool operator>(const Test& t) const{
return this->v_ > t.v_;
}
};
int main(){
int a{ 1 };
int b{ 2 };
std::cout << "max(a, b) : " << ::max(a, b) << '\n';
Test t1{ 10 };
Test t2{ 20 };
std::cout << "max(t1, t2) : " << ::max(t1, t2).v_ << '\n';
}这个例子中创建了一个新的类来运行模板函数max,编译器在编译时只会创建两个的max函数(int和Test),不会创建函数float、double类型的函数,因为你没有使用它们。
int max(int a, int b)
{
return a > b ? a : b;
}
Test max(Test a, Test b)
{
return a > b ? a : b;
}模板参数推导
如果你使用const&来声明函数模板,那么编译器会自动推导传入类型
#include <iostream>
template <typename T> T max(const T &a, const T &b) { return a > b ? a : b; }
int main(int argc, char *argv[]) {
std::cout << max(1, 2) << std::endl;
// cout << _max(1, 2.0) << endl; // 不知道是int还是float/double
std::cout << max<float>(1, 2.0) << std::endl; // 现在知道了
// 使用全局作用域
std::cout << ::max<std::string>("ssss", std::string("乐")) << std::endl; // 现在知道了
std::cout << max(2.0, 3.0) << std::endl;
return 0;
}在模板编程中,decltype可以用于模板参数的类型推导。例如,你可以定义一个函数模板,它接受一个参数并返回该参数的类型:
template<typename T>
decltype(T()) make_instance() {
return T();
}万能引用与引用折叠
所谓的万能引用(又称转发引用),即接受左值表达式那形参类型就推导为左值引用,接受右值表达式,那就推导为右值引用。
template<typename T>
void f(T&&t){}
int a = 10;
f(a); // a 是左值表达式,f 是 f<int&> 但是它的形参类型是 int&
f(10); // 10 是右值表达式,f 是 f<int> 但它的形参类型是 int&&默认实参
就如同函数形参可以有默认值一样,模板形参也可以有默认值。当然了,这里是类型形参
template<typename T = int>
void f();
f(); // 默认为 f<int>
f<double>(); // 显式指明为 f<double>更复杂的:
#include<iostream>
using namespace std::string_literals;
template<typename T1,typename T2,typename RT =
decltype(true ? T1{} : T2{}) >
RT max(const T1& a, const T2& b) { // RT 是 std::string
return a > b ? a : b;
}
int main(){
auto ret = ::max("a", "b"s);
std::cout << ret << '\n';
auto ret2 = ::max(1, 2.0);
std::cout << ret2 << '\n';
}decltype(true ? T1{} : T2{})用于推导T1和T2中较小类型的类型。true没用,使用最后一个表达式的类型,不管条件的真假。
- 为什么需要
T1{},T2{}这种形式?- 必须构造临时对象来写成这种形式,这里其实是不求值语境,我们只是为了写出这样一种形式,让
decltype获取表达式的类型罢了。
- 必须构造临时对象来写成这种形式,这里其实是不求值语境,我们只是为了写出这样一种形式,让
#include <iostream>
int main(int argc, char *argv[]) {
using T = decltype(true ? 1 : 1.2);
using T2 = decltype(false ? 1 : 1.2);
using T3 = decltype(true ? "a" : "b");
std::cout << typeid(T).name() << std::endl;
std::cout << typeid(T2).name() << std::endl;
std::cout << typeid(T3).name() << std::endl;
static_assert(std::is_same<T, double>::value, "T should be int");
static_assert(std::is_same<T2, double>::value, "T2 should be double");
return 0;
}使用 auto 简化
template<typename T,typename T2>
auto max(const T& a, const T2& b) -> decltype(true ? a : b){
return a > b ? a : b;
}std=c++20再次简化
decltype(auto) max(const auto& a, const auto& b) {
return a > b ? a : b;
}非类型模板形参
例如之前使用过的bitset<10005>必须在模板这里声明大小,而不是函数参数中
template<std::size_t N = 10005>
void f() { std::cout << N << '\n'; }
f<100>();重载函数模板
函数模板也可以重载,这里会使用函数重载决议来选择执行哪个函数,基本上优先选择非模板函数
template<typename T>
void test(T) { std::puts("template"); }
void test(int) { std::puts("int"); }
test(1); // 匹配到test(int)
test(1.2); // 匹配到模板
test("1"); // 匹配到模板