SFINAE 模板实例化和特化 template和typename
std::void_t SFINAE(Substitution Failure Is Not An Error匹配失败不是错误),其作用是当我们在进行模板特化的时候,会去选择那个正确的模板,避免失败
boost::mpl boost元编程库
一、函数模板
点击查看代码
#include <iostream>
template <typename T>
T const& Max(T const& a, T const& b)
{
return a > b ? a : b;
}
int main()
{
int a = 1;
int b = 2;
double c = 2.0;
double d = 3.0;
std::string s1 = "Hello";
std::string s2 = "World";
std::cout << "type:" << typeid(Max(a, b)).name() << "\tvalue:" << Max(a, b) << std::endl;
//type:int value:2
std::cout << "type:" << typeid(Max(c, d)).name() << "\tvalue:" << Max(c, d) << std::endl;
//type:double value:3
std::cout << "type:" << typeid(Max(s1, s2)).name() << "\tvalue:" << Max(s1, s2) << std::endl;
//type:class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> > value:World
return 0;
}
二、类模板
点击查看代码
#include <iostream>
#include <vector>
template <typename T>
class MyStack
{
private:
std::vector<T> elems;
public:
void push(T const&);
void pop();
T top()const;
};
template <typename T>
void MyStack<T>::push(T const& e)
{
elems.emplace_back(e);
}
template <typename T>
void MyStack<T>::pop()
{
if (!elems.empty())
elems.pop_back();
else
throw std::out_of_range("out_of_range");
}
template <typename T>
T MyStack<T>::top()const
{
if (elems.empty())
throw std::out_of_range("out_of_range");
return elems.back();
}
int main()
{
try
{
MyStack<int> s1;
s1.push(1);
s1.push(2);
s1.push(3);
std::cout << s1.top() << std::endl; //3
s1.pop();
std::cout << s1.top() << std::endl; //2
MyStack<std::string> s2;
s2.push("string1");
s2.push("string2");
s2.push("string3");
std::cout << s2.top() << std::endl; //string3
s2.pop();
std::cout << s2.top() << std::endl; //string2
s2.pop();
std::cout << s2.top() << std::endl; //string1
s2.pop();
s2.pop(); //此时栈顶没有元素,抛出异常:out_of_range
}
catch (std::exception const& e)
{
std::cout << e.what() << std::endl;
}
return 0;
}
三、关键字typename和class
template <typename T>
等价于template <class T>
更推荐使用:typename
class
用于定义类,在模板引入c++后,最初定义模板的方法为:template<class T>......
在这里class
关键字表明T
是一个类型,后来为了避免class
在这两个地方的使用可能给人带来混淆,所以引入了typename
这个关键字,它的作用同class
一样表明后面的符号为一个类型,这样在定义模板的时候就可以使用下面的方式了:
template<typename T>......
在模板定义语法中关键字class
与typename
的作用完全一样。
typename难道仅仅在模板定义中起作用吗?其实不是这样,typename
另外一个作用为:使用嵌套依赖类型(nested depended name),如下所示:
class MyArray
{
public:
typedef int LengthType;
.....
}
template<class T>
void MyMethod( T myarr )
{
typedef typename T::LengthType LengthType; //LengthType相当于int
LengthType length = myarr.GetLength;
}
这个时候typename
的作用就是告诉c++编译器,typename
后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有typename
,编译器没有任何办法知道T::LengthType
是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。
四、模板实例化
在我们使用类模板时,只有当代码中使用了类模板的一个实例的名字,而且上下文环境要求必须存在类的定义时,这个类模板才被实例化。
-
声明一个类模板的指针和引用,不会引起类模板的实例化,因为没有必要知道该类的定义。
-
定义一个类类型的对象时需要该类的定义,因此类模板会被实例化。
-
在使用sizeof()时,它是计算对象的大小,编译器必须根据类型将其实例化出来,所以类模板被实例化.
-
new表达式要求类模板被实例化。
-
引用类模板的成员会导致类模板被编译器实例化。
-
需要注意的是,类模板的成员函数本身也是一个模板。标准C++要求这样的成员函数只有在被调用或者取地址的时候,才被实例化。用来实例化成员函数的类型,就是其成员函数要调用的那个类对象的类型
1. 显示实例化
当显式实例化模板时,在使用模板之前,编译器根据显式实例化指定的类型生成模板实例。
显示实例化可以避免一定的开销
2.隐式实例化
在使用模板之前,编译器不生成模板的声明和定义实例。只有当使用模板时,编译器才根据模板定义生成相应类型的实例。显式实例化只需声明,不需要重新定义。编译器根据模板实现实例声明和实例定义。
示例代码
点击查看代码
#include <iostream>
template <typename T>
void test(T val1, T val2)
{
std::cout << val1 + val2 << '\n';
}
template void test<float>(float val1, float val2); //显示实例化
int main()
{
test(1, 2);//使用隐式实例化
test(1.0f, 2.2f);
return 0;
}
五、模板特化、偏特化
对模板的参数明确化就是模板特化,偏特化是指对部分参数明确,全特化是指对所有参数明确。模板特化必须有一个主模板。函数模板不能偏特化。
std::enable_if,函数模板多态
调用优先级从高到低进行排序是:全特化 > 偏特化 > 主模板
点击查看代码
#include <iostream>
template <typename T>
struct Fun {
using type = T;
};
template <> // 模板特化,必须有一个主模板,不然报错***不是模板
struct Fun<int> {
using type = unsigned int;
};
int main() {
Fun<long>::type t = 10;
Fun<int>::type t1 = 10; //使用特化版本
return 0;
}
六、默认模板参数
点击查看代码
#include <iostream>
template <typename T = int>
class MyClass
{
public:
void display() { std::cout << typeid(T).name() << '\n'; }
};
template <typename T = float>
void test() { std::cout << typeid(T).name() << '\n'; }
int main()
{
MyClass c1; //使用默认的类型int初始化模板参数
c1.display();
MyClass<char> c2;
c2.display();
test(); //使用默认的参数
test<char>();
return 0;
}
七、C++20 Concepts
模板类型约束
八、std::iterator_traits
可以用来在模板中获取指针指向的值的类型
其他
C++模板函数无法解析的外部符号
在分离式编译的环境下,编译器编译某一个.cpp文件时并不知道另一个.cpp文件的存在,也不会去查找[当遇到未决符号时它会寄希望于连接器]。这种在没有模板的情况下运行良好,但遇到模板时就傻眼了,因为模板仅在需要的时候才会具现化出来,所以,当编译器只看到模板的声明时,它不能具现化该模板,只能创建一个具有外部连接的符号并期待连接器能够将符号的地址决议出来。然而当实现该模板的.cpp文件中没有用到模板的具现体时,编译器懒得去具现,所以,整个工程的.obj中就找不到一行模板具现体的二进制代码,于是连接器也傻眼了