1.延迟推导
Cont中的模板形参可以是不完整类型,因为其内部成员elems为指针类型。
template<typename T>
class Cont {
private:
T* elems;
public:
…
};
但是一旦加了foo()成员函数:
template<typename T>
class Cont {
private:
T* elems;
public:
…
typename std::conditional<std::is_move_constructible<T>::value,
T&&,
T&
>::type
foo();
};
struct Node
{
std::string value;
Cont<Node> next; // only possible if Cont accepts incomplete types
};
foo返回值中对T是否支持转移语义做了判断,is_move_constructible要求T为完整类型,此时若外部使用Cont,就会报编译错误。通过将foo()改为带独立模板形参的模板成员函数,可以达到延迟加载的效果。
template<typename T>
class Cont {
private:
T* elems;
public:
template<typename D = T>
typename std::conditional<std::is_move_constructible<D>::value,
T&&,
T&
>::type
foo();
};
如上,foo()带独立的模板形参D,默认参数为T,将对D的替换试错延迟到foo函数调用时。
2. 构造函数模板
构造函数模板同样导致禁用默认模板类的默认构造函数。为了保证默认构造函数可用,要显示声明默认构造函数为default。
3. 模板形参不能指定默认参数的场景:
4. SFINAE是为了适应C++中的函数重载而有的一种技术,目的是为了在存在多个重载版本的函数时,通过做多次的替换试错,找到最佳匹配的版本。
5. 非类型的模板参数必须满足一个约束:在编译期间能就能够获取到值。若是在运行期间才能够获取到的值,如局部变量地址,则与模板的定义不想匹配,模板实例化是在编译期间完成的。
6. 对模板费类型参数赋值时,接收隐式转换,或定义了constexpr的类型转换表达式才允许不同类型的常量赋值。子类指针赋值给父类指针是不允许的。
7. 模板模板形参只能用class关键字,不能用typename。模板的模板模板类型的实参中的默认参数是被忽略的,如:
#include
// declares in namespace std:
// template>
// class list;
template<typename T1, typename T2,
template<typename> class Cont> // Cont expects one parameter
class Rel {
…
};
Rel<int, double, std::list> rel; // ERROR before C++17: std::list has more than one template parameter
但当前模板的模板模板形参的默认参数在编译时是会参与评估的。
8. 函数模板实例化后的覆盖替换规则,同名的模板函数不会覆盖普通函数。
9. 对变化参数模板中参数类型的操作成为Pattern,其可以是衍生新的类型,如T*…,或是基于类型的表达式,如f(T)。该表达式可能非常复杂。在类型pack展开时候,该pattern会重复地逐个作用在包内的每个类型上。
10. 类模板的友元类
类模板的友元声明有如下规则:
1) 如果是类模板的部分示例作为友元,则在类模板内的友元声明之前必须以声明,并在当前范围内可见;普通的非模板类做为友元无此约束;
2) 友元类不能有定义,只能是声明;
template <typename T1>
class B1;
template <typename Ty>
class A
{
friend class B1<Ty>; // 部分实例作为友元,必须要有之前的声明在,否则编译报错;
friend class B2; //普通类作为友元,不需要之前存在类声明,可以是首个声明
template <typename T2>
friend class B3; //首次声明的类模板作为友元
};
不同模板形参的实例化类,可以认为是完全不同的类,类模板中声明为私有的函数是不能够直接访问的,若要访问必须声明为友元,如:
template <typename T>
class A
{
public:
template<typename T2>
void swap(const A<T2>& other)
{
other.test();
std::cout << "swap" << std::endl;
}
// 若无此友元声明则会报编译错误,
// 因为A和A是两个完全不同的模板类,不可访问私有声明的成员函数
template <typename T3>
friend class A;
private:
void test() const
{
std::cout << "test()" << std::endl;
}
};
void testFriendClass
{
A<int> a;
A<double> b;
a.swap(b);
}
12. 类的友元函数
特化的模板函数,作为友元声明时,其后一定要带尖括号,这是语法上的要求。如果实例化时能够从函数的形参类型中推导出模板形参类型,函数模板的实参列表也要保留"<>"。
template <typename T> void f(T t);
class BL
{
friend void f<int>(int);
friend void f<>(int);
};
template <typename T>
class C
{
static_assert(std::is_same<T, int>::value, "got here");
friend void f1();
friend void f2(C<T>* const);
};
void testADL()
{
// 如果只有此句是不会触发类模板实例化的
C<float>* ptr_c = nullptr;
// 该符号查找不到,因为其没有入参,adl也就无法生效
f1();
// 该处会引起C模板的实例化,借助ADL能够在C的类作用域内找到该符号。
f2(p);
}
template <typename T>
class Node
{
using Type = T;
Node* next; // Node即为current instiation,等价于Node,T为模板实参
Node<Type>* privous;
// 未知特化,这样的类型引入是不允许的
Node<T*> parent;
};
template<typename T>
class Shell {
public:
template<int N>
class In {
public:
template<int M>
class Deep {
public:
virtual void f();
};
};
};
template<typename T, int N>
class Weird {
public:
void case1(
// 通过template关键字指定类型名称为依赖名称
typename Shell<T>::template In<N>::template Deep<N>* p) {
p->template Deep<N>::f(); // inhibit virtual call
}
void case2(
typename Shell<T>::template In<N>::template Deep<N>& p) {
p.template Deep<N>::f(); // inhibit virtual call
}
};
template <typename T>
class BXT
{
public:
using Mystery = T;
template <typename U>
struct Magic;
// 成员函数
void f();
};
template <typename T>
class DXTT : private BXT<T> //私有继承,除了using导入的符号外,其余基类符号不可访问
{
public:
using BXT<T>::f;
using typename BXT<T>::Mystery;
Mystery* p;
};
this->
或加基类命名限定的方式来强制类型查找,从而将符号查找提前至模板解析阶段。示例代码如下:class NonDep {
public:
using Type = int;
};
template<typename T>
class Dep {
public:
using OtherType = T;
};
template<typename T>
class DepBase : public NonDep, public Dep<T> {
public:
void f() {
typename DepBase<T>::Type t; // finds NonDep::Type;
// typename keyword is optional
typename DepBase<T>::OtherType* ot; // finds nothing; DepBase::OtherType is a member of an unknown specialization
}
};
对依赖类模板而言,实现上应用的接口或变量在模板形参未确定之前存在性是未知额,只有到了实例化阶段,模板实参确定后,才能能够zh知道名称是否存在。
18. 类模板实例化
类模板采用延迟实例化,或懒惰实例化的策略,在不需要知道模板类的完全定义时,仅做部分实例化(partical institantation)。隐式实例化模板类,在实例化过程中会对类定义内的如下内容做实例化校验:
1) 类内的union或class定义、typedef声明和成员变量定义等;
2) 对成员函数仅做函数声明的校验,包括成员函数入参和返回值的合法性校验;函数提内的类型合法性仅在调用该成员函数时校验;但是对于虚函数例外,其在类实例化时,就会对函数体内的类型做合法性校验。因为对于虚函数调用机制而言,构造函数虚表时,要求虚函数一定是作为可链接实体存在的。
测试验证的代码如下:
template <typename T>
class InstantationTest
{
public:
typedef T::ValuType VT; //#1 类模板实例化时就会校验
void candidate()
{
T::ValueType v; //#2 普通函数:类模板实例化时不做校验,仅在函数调用时校验
}
void candidate(T::ValueType t); //#3 类模板实例化时会做校验
{
T::ValueType v; //#4 类模板实例化时不做校验,仅在函数调用时校验
}
virtual void candiate1()
{
T::ValueType v; //#5类模板实例化时就会做函数体内的类型合法性校验,
}
virtual void candidate(T t = "aaa") //#5.1 默认参数赋值也是不做合法性校验的,仅有在使用调用该函数,并且用到了默认参数才校验;
{
}
private:
T::ValueType value; //#6 类模板实例化时就会校验
};
void check()
{
InstantationTest<int>* ptr_it; //#7 对类模板做部分实例化,不需要知道其完整的类型定义
InstantationTest<int> it; //#8 需要能够访问到实例化类的完整定义
}
#include
void func(void*) { std::puts("The call resolves to void*") ;}
template<typename T> void g(T x)
{
func(0);
}
void func(int) { std::puts("The call resolves to int"); }
int main()
{
// 在对模板函数g的调用处模板函数实例化,并做名称查找,导致func的引用出错。
g(3.14);
}
为了解决此类问题,在名称绑定上,做了依赖名称和非依赖名称的划分。按照c++标准,名称包含如下三种:
#include
void func(long) { std::puts("func(long)"); }
template <typename T> void meow(T t) {
func(t);
}
void func(int) { std::puts("func(int)"); }
namespace Kitty {
struct Peppermint {};
void func(Peppermint) { std::puts("Kitty::func(Kitty::Peppermint)"); }
}
int main() {
meow(1729); // T为int类型,为全局作用域,所以仅做普通查找,从而匹配到到模板定义之前的函数func(long)
Kitty::Peppermint pepper;
meow(pepper);// T为Kitty::Peppermint类型,通过ADL查找匹配到模板定义之后的函数名称func(Peppermint)
}
参考如下文章
二阶段名称查找的MSVC支持
20. 参数依赖查找(ADL)
ADL查找,即在函数符号的查找中会扩大名称查找范围做形参所在作用域的符号查找,模板查找还包括模板形参。适用的2个条件:
a. 函数名称必须是非限定名称;
b. 表达式必须为函数调用,否则不会触发ADL;
若参数为int的全局作用域,则匹配不到特定作用域内的符号名称。示例代码如下:
namespace Deep
{
struct A {};
void func(const A& a) //#1
{
std::cout << "This is func(A);" << std::endl;
}
void func(const std::string& str) #2
{
std::cout << "This is func(std::string)" << std::endl;
}
}
void main()
{
Deep::A a;
func(a); //匹配#1位置的符号,通过a所在作用域查找到匹配符号;
std::string str("test");
func(str); //报错无此符号;
system("pause");
}
ADL
21. 函数模板的几种形参上的差别
template <typename T>
void TestParamType(T t); //#1
template <typename T>
void TestParamType(T& t); //#2
template <typename T>
void TestParamType(T&& t);//#3
#1-非引用类型,有点类似传参时参数复制的意思,所以若入参为引用类型会退化为非引用类型,若为数组则退化为指针;
#2-左值引用类型,语义上允许函数内部修改,仅接受左值,不接受右值。数组类型不会退化。
#3-前向引用(右值引用)类型,既可以接收左值又可以接受右值,类型推导时,会根据实参类型做引用折叠。数组类型同样不会退化。
完美转发与之相关,目的是为了间接函数调用时,实现参数的准确透传, 透传时保留参数的引用类型。在标注库中make_shared等,都是应用的实例。
22. SFINAE中的Immediate Context
在函数模板的替换中,存在即时上下文的概念,只有在即时上下文中中出现无效类型、表达式或符号歧义才会仅踢出候选,不编译错误。非即时上下文则直接报错,书中整理了非即时上下文:
最常见的就前两个,类模板的定义和函数模板的函数体。示例代码如下:
template<typename T>
class Array {
public:
using iterator = T*;
};
template<typename T>
void f(Array<T>::iterator first, Array<T>::iterator last);
template<typename T>
void f(T*, T*);
int main()
{
f<int&>(0, 0); // ERROR: substituting int& for T in the first functiontemplate
} // instantiates Array, which then fails
在替换过程中,第一个候选,校验Array中是否存在iterator成员类型时,要首先实例化模板类,Array
该技术起初是用于多个重载版本的函数模板筛选,现在既可以用于函数模板筛选,也可用于类模板特化版本的筛选。
23. auto类型推导
表达式中auto的推导可等价同形式的函数模板函数形参的推导。在推导中也有auto,auto&和auto&&三种形式,对单独的auto是不能够对应引用类型的,因为推导时的类型退化。同时auto关键字的使用位置也有约束:不能出现在函数模板形参位置或紧跟类型说明符之后作为声明的一部分。
template<typename T> struct X { T const m; };
auto const N = 400u; // OK: constant of type unsigned int
auto* gp = (void*)nullptr; // OK: gp has type void*
auto const S::*pm = &X<int>::m; // OK: pm has type int const X::*
X<auto> xa = X<int>(); // ERROR: auto in template argument
int const auto::*pm2 = &X<int>::m; // ERROR: auto is part of the “declarator”
template <typename T>
struct RemoveRef
{
using Type = T;
}
template <typename T>
struct RemoveRef<T&>
{
using Type = T;
}
template <typename T>
using RemoveRefT = typename RemoveRef<T>::Type;
/*******************/
template <typename T>
struct IsInt { static const bool value = false; };
template<>
struct IsInt<int> { static const bool value = true; };
template <typename T>
constexpr bool IsIntV = IsInt<T>::value;
#include
template<typename T1, typename T2>
struct PlusResultT {
//通估declval和decltype推导两类型实例加和的结果类型
using Type = decltype(std::declval<T1>() + std::declval<T2>());
};
template<typename T1, typename T2>
using PlusResult = typename PlusResultT<T1, T2>::Type;