【深蓝学院C++】第12章 模板 笔记

模板

1.函数模板

(1)使用 template 关键字引入模板

​ template void fun(T) {…}

​ 1 函数模板的声明与定义

​ 2 typename 关键字可以替换为 class ,含义相同

​ 3 函数模板中包含了两对参数:函数形参 / 实参;模板形参 / 实参

(2)函数模板的显式实例化: fun(3)

​ 1 实例化会使得编译器产生相应的函数(函数模板并非函数,不能调用)

​ 2 编译期的两阶段处理

​ 模板语法检查

​ 模板实例化

​ 3 模板必须在实例化时可见 – 翻译单元的一处定义原则

​ 4 注意与内联函数的异同

#include <iostream>
template <typename T>
void fun(T input)
{
    std::cout << input << std::endl;
}

int main()
{
    fun<int>(3);
    fun<double>(3.0);
}

(3)函数模板的重载

#include <iostream>
template <typename T>
void fun(T input)
{
    std::cout << input << std::endl;
}

template <typename T, typename T2>
void fun(T input, T2 input2)
{
    std::cout << input << std::endl;
    std::cout << input2 << std::endl;
}

int main()
{
    fun<int>(3);
    fun<double>(3.0);
}

(4)模板实参的类型推导

​ 如果函数模板在实例化时没有显式指定模板实参,那么系统会尝试进行推导

​ 推导是基于函数实参(表达式)确定模板实参的过程,其基本原则与 auto 类型推导相似

​ 1 函数形参是左值引用 / 指针:

​ 忽略表达式类型中的引用

​ 将表达式类型与函数形参模式匹配以确定模板实参

#include <iostream>
template <typename T>
void fun(T& input)
{
    std::cout << input << std::endl;
}

int main()
{
    int y = 3;
    int& x = y;
    fun(x);
    /*
    	x -> int& -> int
    	input -> T&
    */
}

​ 2 函数形参是万能引用

​ 如果实参表达式是右值,那么模板形参被推导为去掉引用的基本类型

​ 如果实参表达式是左值,那么模板形参被推导为左值引用,触发引用折叠

#include <iostream>
template <typename T>
void fun(T&& input)
{
    std::cout << input << std::endl;
}

int main()
{
    fun(3);

    int y = 4;
    fun(y);     // T -> int&, int& && -> int&
}

​ 3 函数形参不包含引用

​ 忽略表达式类型中的引用

​ 忽略顶层 const

​ 数组、函数转换成相应的指针类型

#include <iostream>
template <typename T>
void fun(T input)
{
    std::cout << input << std::endl;
}

int main()
{
    fun(3);

    int x = 3;
    const int& ref = x;
    fun(ref);

    const int* const ptr = &x;
    fun(ptr);       //const int*

}
#include <iostream>
template <typename T>
void fun(T input, T input2)
{
    std::cout << input << std::endl;
    std::cout << input2 << std::endl;
}

int main()
{
    fun<int>(2, 4.5);

}

(5)模板实参并非总是能够推导得到

​ 1 如果模板形参与函数形参无关,则无法推导

​ 2 即使相关,也不一定能进行推导,推导成功也可能存在因歧义而无法使用

template <typename T = double>
void fun(T x, T y)
{
    
}
int main()
{
    fun(3, 5.0);
}

(6)在无法推导时,编译器会选择使用缺省模板实参

​ 可以为任意位置的模板形参指定缺省模板实参–注意与函数缺省实参的区别

template <typename T = int>
void fun(unsigned x = sizeof(T))
{

}

int main()
{
    fun(2);

}
#include <iostream>
#include <type_traits>
template <typename res = double, typename T>
res fun(T x, T y)
{

}

int main()
{
    fun(2,5);
}

(7)显式指定部分模板实参

​ 1 显式指定的模板实参必须从最左边开始,依次指定

​ 2 模板形参的声明顺序会影响调用的灵活性

template <typename res = double, typename T>
res fun(T x, T y)
{

}

int main()
{
    fun<int, int>(2,5);
}
template <typename res, typename T>
res fun(T x, T y)
{}
int main()
{
    fun<int>(2,5);
}

(8)函数模板制动推导时会遇到的几种情况

​ 1 函数形参无法匹配 SFINAE (替换失败并非错误)

template <typename T>
void fun(T x, T y){}

template <typename T, typename T2>
void fun(T x, T2 y){}
int main()
{
    fun<int>(2,5.0);
}

​ 2 模板与非模板同时匹配,匹配等级相同,此时选择非模板的版本

#include <iostream>
template <typename T, typename T2>
void fun(T x, T2 y)
{
    std::cout << 1 << std::endl;
}
void fun(int x, double y)
{
    std::cout << 2 << std::endl;
}
int main()
{
    fun(2, 5.0);
}

​ 3 多个模板同时匹配,此时采用偏序关系确定选择“最特殊”的版本

#include <iostream>
template <typename T, typename T2>
void fun(T x, T2 y)
{
    std::cout << 1 << std::endl;
}

template <typename T>
void fun(T x, float y)
{
    std::cout << 2 << std::endl;
}

int main()
{
    fun(2,5.0f);
}
#include <iostream>
template <typename T>
void fun(T x)					// A	T可以匹配A,也可以匹配B*
{
    std::cout << 1 << std::endl;
}
template <typename T>
void fun(T* x)					// B*	T*不可以匹配A,可以匹配B*,所以更“特殊”
{
    std::cout << 2 << std::endl;
}

int main()
{
    int x = 3;
    fun(&x);
}

(9)函数模板的实例化控制

​ 1 显式实例化定义: template void fun(int) / template void fun(int)

#include <iostream>
template <typename T>
void fun(T x)
{
    std::cout << x << std::endl;
}

template void fun<int> (int x);

int main()
{
    int x = 3;
    fun<int>(x);
}

​ 2 显式实例化声明: extern template void fun(int) / extern template void fun(int)

​ 3 注意一处定义原则

​ 4 注意实例化过程中的模板形参推导

#include <iostream>
template <typename T>
void fun(T x)
{
    std::cout << x << std::endl;
}
template<typename T>
void fun(T * x)
{
    std::cout << x <<std::endl;
}
template void fun(int * x);
template void fun<int *>(int * x);

int main()
{

}

(10)函数模板的 ( 完全 ) 特化: template<> void f(int) / template<> void f(int)

​ 1 并不引入新的(同名)名称,只是为某个模板针对特定模板实参提供优化算法

​ 2 注意与重载的区别

​ 3 注意特化过程中的模板形参推导

#include <iostream>
template <typename T>
void fun(T x)
{
    std::cout << x << std::endl;
}
template<>
void fun(int x)
{
    std::cout << x + 1<<std::endl;
}

int main()
{
    fun(3.0);
}

(11)避免使用函数模板的特化

​ 1 不参与重载解析,会产生反直觉的效果

#include <iostream>
template <typename T>
void fun(T x)
{
    std::cout << 1 << std::endl;
}
template <typename T>
void fun(T* x)
{
    std::cout << 2 << std::endl;
}
template<>
void fun(int* x)
{
    std::cout << 3 <<std::endl;
}

int main()
{
    int x;
    fun(&x);
}
#include <iostream>
template <typename T>
void fun(T x)
{
    std::cout << 1 << std::endl;
}
template <typename T>
void fun(T* x)
{
    std::cout << 2 << std::endl;
}
template<>
void fun<int*>(int* x)
{
    std::cout << 3 <<std::endl;
}

int main()
{
    int x;
    fun(&x);
}

​ 2 通常可以用重载代替

#include <iostream>
template <typename T>
void fun(T x)
{
    std::cout << 1 << std::endl;
}
template <typename T>
void fun(T* x)
{
    std::cout << 2 << std::endl;
}

void fun(int* x)
{
    std::cout << 3 <<std::endl;
}

int main()
{
    int x;
    fun(&x);
}

​ 3 一些不便于重载的情况:无法建立模板形参与函数形参的关联

#include <iostream>
template <typename T, typename res>
res fun(T x)
{
    std::cout << 1 << std::endl;
    return res{};
}
template <typename T>
void fun(T* x)
{
    std::cout << 2 << std::endl;
}

int main()
{
    int x;
    fun(&x);
}

​ 使用 if constexpr 解决

#include <iostream>
#include <type_traits>
template <typename res, typename T>
res fun(T x)
{
    if constexpr(std::is_same_v<res, int>)
    {
        std::cout << 1 << std::endl;
    } else
    {
        std::cout << 2 << std::endl;
    }
    return res{};
}

int main()
{
    int x;
    fun<int>(&x);
    fun<float>(&x);
}

​ 引入"假"函数形参

​ 通过类模板特化解决

(12)(C++20) 函数模板的简化形式:使用 auto 定义模板参数类型

​ 1 优势:书写简捷

​ 2 劣势:在函数内部需要间接获取参数类型信息

#include <iostream>
void fun(auto x)
{
    
}
int main()
{
    int x;
    fun(&x);

}

2.类模板与成员函数模板

(1)使用 template 关键字引入模板

​ template class B {…};

template <typename T>
class B
{
    
};
int main()
{
    B<int> x;
    B<char> y;

}

​ 1 类模板的声明与定义 翻译单元的一处定义原则

​ 2 成员函数只有在调用时才会被实例化

#include <iostream>
template <typename T>
class B
{
public:
    void fun(T input)
    {
        std::cout << input << std::endl;
    }
};
struct Str{};
int main()
{
    B<int> x;
    x.fun(3);
    
    B<Str> y;
}

​ 3 类内类模板名称的简写

#include <iostream>
template <typename T>
class B
{
public:
    auto fun()
    {
        return B{};     //return B<T>{};
    }
};
struct Str{};
int main()
{
    B<int> x;
    x.fun();
}

​ 4 类模板成员函数的定义(类内、类外)

#include <iostream>
template <typename T>
class B
{
public:
    void fun();

};
template<typename T>
void B<T>::fun()
{
    
}
int main()
{
    B<int> x;
    x.fun();
}

(2)成员函数模板

​ 1 类的成员函数模板

#include <iostream>

class B
{
public:
    template <typename T>
    void fun()
    {
        
    }

};

int main()
{
    B x;
    x.fun<int>();
}
#include <iostream>
class B
{
public:
    template <typename T>
    void fun();
};
template <typename T>
void B::fun() {
    
}

int main()
{
    B x;
    x.fun<int>();
}

​ 2 类模板的成员函数模板

#include <iostream>
template <typename T>
class B
{
public:
    template <typename T2>
    void fun()
    {

    }
};


int main()
{
    B<int> x;
    x.fun<float>();
}
#include <iostream>
template <typename T>
class B
{
public:
    template <typename T2>
    void fun();
};
template <typename T>
template <typename T2>
void B<T>::fun() 
{
    
}

int main()
{
    B<int> x;
    x.fun<float>();
}

(3)友元函数(模板)–使用较少

​ 1 可以声明一个函数模板为某个类(模板)的友元

​ 2 C++11 支持声明模板参数为友元

#include <iostream>
template <typename T>
class B
{
public:
    template <typename T2>
    friend void fun()
    {

    }
};


int main()
{ 
    B<int> x;

}
#include <iostream>
template <typename T>
class B
{
public:
    friend void fun(B input)
    {
        std::cout << input.x << std::endl;
    }
private:
    int x = 3;
};


int main()
{
    B<int> val;
    fun(val);
}

(4)类模板的实例化

​ 与函数实例化很像,可以实例化整个类,或者类中的某个成员函数

namespace N 
{
    template<class T> 
    class Y // template definition
    { 
        void mf() {} 
    }; 
}
 
// template class Y<int>; // error: class template Y not visible in the global namespace
using N::Y;
// template class Y<int>; // error: explicit instantiation outside 
                          // of the namespace of the template
template class N::Y<char*>;       // OK: explicit instantiation
template void N::Y<double>::mf(); // OK: explicit instantiation

(5)类模板的(完全)特化 / 部分特化(偏特化)

​ 特化版本与基础版本可以拥有完全不同的实现

​ 完全特化:

#include <iostream>
template <typename T>
class B
{
public:
    void fun()
    {
        std::cout << 1 << std::endl;
    }

};
template <>
class B<int>
{
public:
    void fun()
    {
        std::cout << 2 << std::endl;
    }
};

int main()
{
    B<int> x;
    x.fun();
}

​ 部分特化:

#include <iostream>
template <typename T, typename T2>
class B
{
public:
    void fun()
    {
        std::cout << 1 << std::endl;
    }

};
template <typename T2>
class B<int, T2>
{
public:
    void fun()
    {
        std::cout << 2 << std::endl;
    }
};

int main()
{
    B<int, double> x;
    x.fun();
}
#include <iostream>
template <typename T>
class B
{
public:
    void fun()
    {
        std::cout << 1 << std::endl;
    }

};
template <typename T>
class B<T*>
{
public:
    void fun()
    {
        std::cout << 2 << std::endl;
    }
};

int main()
{
    B<int*> x;
    x.fun();
}

(6) 类模板的实参推导(从 C++17 开始)

​ 1 基于构造函数的实参推导

#include <iostream>
template <typename T>
class B
{
public:
    B(T input){}
    void fun()
    {
        std::cout << 1 << std::endl;
    }

};

int main()
{
    B x(3);
    x.fun();
}

​ 2 用户定义的推导指引

// 模板的声明
template<class T>
struct container
{
    container(T t) {}
 
    template<class Iter>
    container(Iter beg, Iter end);
};
 
// 额外的推导指引
template<class Iter>
container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>;
 
// 使用
container c(7); // OK:用隐式生成的指引推导出 T=int
std::vector<double> v = {/* ... */};
auto d = container(v.begin(), v.end()); // OK:推导出 T=double
container e{5, 6}; // 错误:std::iterator_traits<int>::value_type 不存在

​ 3 注意:引入实参推导并不意味着降低了类型限制!

​ 4 C++ 17 之前的解决方案:引入辅助模板函数

#include <iostream>
#include <utility>
template <typename T1, typename T2>
std::pair<T1, T2> make_pair(T1 val1, T2 val2)
{
    return std::pair<T1, T2>(val1, val2);
}

int main()
{
    auto x = make_pair(3, 3.14);
}

3.Concepts

(1)模板的问题:没有对模板参数引入相应的限制

​ 1 参数是否可以正常工作,通常需要阅读代码进行理解

​ 2 编译报错友好性较差 (vector<int&>)

(2)( C++20 ) Concepts :编译期谓词,基于给定的输入,返回 true 或 false

#include <iostream>
#include <type_traits>
template <typename T>
concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;

int main()
{
    std::cout << IsAvail<int> <<std::endl;
    std::cout << IsAvail<char> << std::endl;
}

​ 1 与 constraints ( require 从句)一起使用限制模板参数

​ 2 通常置于表示模板形参的尖括号后面进行限制

#include <iostream>
#include <type_traits>
template <typename T>
concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;

template <typename T>
    requires IsAvail<T>
void fun(T input)
{
                
}
int main()
{
    fun(3);
    //fun(true);
}

(3)Concept 的定义与使用

​ 1 包含一个模板参数的 Concept

​ 使用 requires 从句

template <typename T>
    requires IsAvail<T>
void fun(T input)
{
                
}

​ 直接替换 typename

template <IsAvail T>
void fun(T input)
{
                
}

​ 2 包含多个模板参数的 Concept

​ 用做类型 constraint 时,少传递一个参数,推导出的类型将作为首个参数

#include <iostream>
#include <type_traits>
template <typename T, typename T2>
concept IsAvail = std::is_same_v<T, T2>;

template <typename T, typename T2>
    requires IsAvail<T, T2>
void fun(T input, T2 input2)
{

}
int main()
{
    fun(3, 5);
}
#include <iostream>
#include <type_traits>
template <typename T, typename T2>
concept IsAvail = std::is_same_v<T, T2>;

template <typename T>
    requires IsAvail<T, int>
void fun(T input)
{

}
int main()
{
    fun(3);
}
#include <iostream>
#include <type_traits>
template <typename T, typename T2>
concept IsAvail = std::is_same_v<T, T2>;

template <IsAvail<int> T>
    requires IsAvail<T, int>
void fun(T input)
{

}
int main()
{
    fun(3);
}

(4)requires 表达式

​ 1 简单表达式:表明可以接收的操作

template <typename T>
concept Addable = requires(T a, T b)
{
    a + b;
};

template <Addable T>
auto fun(T x, T y)
{
    return x + y;
}
int main()
{
    fun(3, 5);
}

​ 2 类型表达式:表明是一个有效的类型

template <typename T>
concept Avail = requires
{
    typename T::inter;
};

template <Avail T>
auto fun(T x)
{
    
}
struct Str
{
    using inter = int;
};
int main()
{
    fun(Str{});
}

​ 3 复合表达式:表明操作的有效性,以及操作返回类型的特性

//明明一样却还是报错。。。。。。。。。。。。
#include <iostream>
#include <type_traits>
template <typename T>
concept Avail =
requires (T x)
{
    {x + 1} -> int;
};

template <Avail T>
auto fun(T x)
{

}

int main()
{
    fun(3);
}

​ 4 嵌套表达式:包含其它的限定表达式

(5)requires 从句会影响重载解析与特化版本的选取

​ 只有 requires 从句有效而且返回为 true 时相应的模板才会被考虑

​ requires 从句所引入的限定具有偏序特性,系统会选择限制最严格的版本

#include <iostream>
#include <type_traits>

template <typename T>
    requires std::is_same_v<T, float>
void fun(T input)
{
    std::cout << 1 << "\n";
}
template <typename T>
    requires std::is_same_v<T, int>
void fun(T input)
{
    std::cout << 2 << "\n";
}
int main()
{
    fun(3);
}
#include <iostream>
#include <type_traits>

template <typename T>
concept C1 = std::is_same_v<T, int>;
template <typename T>
concept C2 = std::is_same_v<T, int> || std::is_same_v<T, int>;

template <C1 T>
void fun(T input)
{
    std::cout << 1 << "\n";
}
template <C2 T>
void fun(T input)
{
    std::cout << 2 << "\n";
}
int main()
{
    fun(3);
}

(6)特化小技巧:在声明中引入 "A||B” 进行限制,之后分别针对 A 与 B 引入特化

#include <iostream>
#include <type_traits>

template <typename T>
    requires std::is_same_v<T, int> || std::is_same_v<T, float>

class B;

template <>
class B<int> {};

template <>
class B<float> {};
int main()
{
    B<double> x;
}

4.模板相关内容

(1)数值模板参数与模板模板参数

​ 1 模板可以接收(编译期常量)数值作为模板参数

​ template class Str;

#include <iostream>

template<int a>
int fun(int x)
{
    return x + a;
}
int main()
{
    std::cout << fun<3>(5);
}

​ template <typename T, T value> class Str;

#include <iostream>

template<typename T, T a>
int fun(int x)
{
    return x + a;
}
int main()
{
    std::cout << fun<int, 3>(5);
}

​ (C++ 17) template class Str;

#include <iostream>
template<auto a>
int fun()
{
}
int main()
{
    fun<3>();
    fun<true>();
}

​ (C++ 20) 接收字面值类对象与浮点数作为模板参数

​ 2 接收模板作为模板参数

​ template <template class C> class Str;

#include <iostream>
template<typename T>
class C{};

template <template<typename T> class T2>
void fun()
{
}
int main()
{
    fun<C>();
}

​ (C++17) template <template typename C> class Str;

​ C++17 开始,模板的模板实参考虑缺省模板实参

(2)别名模板与变长模板

​ 1 可以使用 using 引入别名模板

​ 为模板本身引入别名

#include <iostream>
template<typename T>
using MyType = T;
int main()
{
    MyType<int> t;
}
template<class T>
struct Alloc { };
template<class T>
using Vec = vector<T, Alloc<T>>; // 类型标识为 vector<T, Alloc<T>>
Vec<int> v; // Vec<int> 等同于 vector<int, Alloc<int>>

​ 为类模板的成员引入别名

template<typename T>
struct B
{
    using TP = T*;
};
template <typename T>
using MyPointer = typename B<T>::TP;

int main()
{
    MyPointer<int> t;
}

​ 别名模板不支持特化,但可以基于类模板的特化引入别名,以实现类似特化的功能

​ 2 变长模板( Variadic Template )

​ 变长模板参数与参数包

#include <iostream>
template<int... a>
void fun()
{

}

int main()
{
    fun<1, 2, 3>();
}
#include <iostream>
template<typename ... a>
void fun()
{

}

int main()
{
    fun<double, int, char>();
}

​ 变长模板参数可以是数值、类型或模板

​ sizeof… 操作

#include <iostream>
template<typename ...T>
void fun(T... args)
{
    std::cout << sizeof ...(args) << std::endl;
}
int main()
{
    fun(2, 4.5, 'c');
}

​ 注意变长模板参数的位置

(3)包展开与折叠表达式

template<typename ...T>
void fun(T... args)     //T...  ->  int, double, char
{
    std::cout << sizeof ...(args) << std::endl;
}
int main()
{
    fun(2, 4.5, 'c');
}

​ 1 (C++11) 通过包展开技术操作变长模板参数
​ – 包展开语句可以很复杂,需要明确是哪一部分展开,在哪里展开
​ 2 (C++17) 折叠表达式 (cpp reference)
​ – 基于逗号的折叠表达式应用
​ – 折叠表达式用于表达式求值,无法处理输入(输出)是类型与模板的情形

#include <iostream>
template<typename ...T>
void fun(T... args)
{
    ((std::cout << args << std::endl), ...);
}
int main()
{
    fun(2, 4.5, 'c', "hello");
}

(4)完美转发与 lambda 表达式模板

​ 1 (C++11) 完美转发: std::forward 函数

​ 通常与万能引用结合使用

​ 同时处理传入参数是左值或右值的情形

#include <iostream>
#include <utility>
void g(int&)
{
    std::cout << "l-reference" << std::endl;
}
void g(int&&)
{
    std::cout << "r-reference" << std::endl;
}
template <typename T>
void fun(T&& input)
{
    std::cout << "hello\n";
    g(std::forward<T>(input));
}
int main()
{
    int x = 3;
    fun(x);
    fun(3);
}

​ 2 (C++20) lambda表达式模板

(5)消除歧义与变量模板

​ 1 使用 typename 与 template 消除歧义

​ 使用 typename 表示一个依赖名称是类型而非静态数据成员

#include <iostream>
struct Str
{
    using internal = int;
};
struct Str2
{
    //inline const static internal = 100;
};

template<typename T>
void fun()
{
    typename T::internal* p;
}
int main()
{
    fun<Str>();
    //fun<Str2>();
}

​ 使用 template 表示一个依赖名称是模板

​ template 与成员函数模板调用

​ 2 (C++14) 变量模板

​ template T pi = (T)3.1415926;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值