函数模板

函数模板不是函数,只有实例化函数模板,编译器才能生成实际的函数定义。不过在很多时候,它看起来就像普通函数一样,你可以把它当作一系列相同功能的函数的集合,编译器会在编译时生成这些函数,而不是手动为每个类型编写。

定义

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函数(intTest),不会创建函数floatdouble类型的函数,因为你没有使用它们。

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{}) 用于推导 T1T2 中较小类型的类型。
    • 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");      // 匹配到模板

类模板

变量模板

模板全特化

模板偏特化

参考

  1. https://mq-b.github.io/Modern-Cpp-templates-tutorial/md/%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/01%E5%87%BD%E6%95%B0%E6%A8%A1%E6%9D%BF