C++11泛型编程

C++11型别推导

数组类型与指针类型的转换

C++有一个一直以来大家都习以为常的特性,那就是数组名字就是数组首元素的指针。一个数组形参可以接受一个指向数组首元素指针的实参,看起来这两者是一样的,但是其实进行了某种程度的退化,如果使用不当,很可能会造成析构不当(数组部分元素没有被析构到)。注意有一种情况下,数组是不会退化成指针(也就是保留其大小信息), 那就是被传引用调用时。

数组其实是有型别的,比如说const char name[]="hello";的型别就是const char [6], 它的引用版本(作为函数参数的时候)是 const char (&)[6]

1
2
3
template <typename T>
void f(T& param);
void g(T param)

传引用调用的时候,利用数组类型带大小这个特点,我们能够在编译器就能够获得传入数组的大小,利用的是非类型模板参数。

1
2
3
4
5
template <typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
return N;
}

auto关键字

auto关键字在很多情况下和模板类型的推导是一致的,以下这种情况却需要区别对待,那就是initializer_list这一个新出的初始化变量的方法。(以后我们分别将这两种类型推导叫做模板类型推导和auto类型推导。)

1
2
3
4
auto x1=27;  //auto为int
auto x2(27); // auto 为int
auto x3{27} // auto为std::initializer_list<int>, 且只有一个元素,27
auto x4={27} //同上

auto推导会将大括号推导为initializer_list,这一点和其他推导是不同的,比如:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;
auto x={11, 12, 13};
template <typename T>
void f(T param){
std::cout << "I am called" << std::endl;
};
int main()
{
f({11, 12, 13}); // 错误,无法通过编译,默认不会讲花括号内转换成一个initializer_list类型。
f(x); //正确, 输出I am called
}

另一个需要注意的是,auto用作函数返回值时,采用的是模板类型推导而不是auto类型推导,所以返回类似于{1, 2, 3}这样的initializer_list也是不被允许的。

还有一个容易被混淆的点就是auto非常类似于 模板类型推导,但是不同于函数模板参数中会保留修饰词如const, auto并不会保留(函数按值传递不保留修饰词,这里也一样,auto是复制变量时,就不会保留修饰词)。要想要保留修饰词,需要使用decltype关键字。

1
2
3
4
Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // myWidget1是Widget类型,不再具有const和引用
decltype(auto) myWidget2 = cw; // myWidget2是const Widget&类型,能够保留修饰词

实际的编程中,应该多用auto来代替显式指定变量类型,以避免没必要的隐式的强制类型装换。

decltype关键字

decltype一般用于返回值类型随着输入参数变化的函数,它的作用就是返回一个表达式或者一个值得型别。

decltype与C++11中的返回值型别尾序语法(trailing return type syntax)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <vector>
using namespace std;
template <typename T, typename Index>
auto f(const T& container, Index i) -> decltype(container[i])
{
std::cout << container[i] << std::endl;
return container[i];
}


int main()
{
std::vector<int> v{11, 12, 13};
f(v, 0); //正确, 输出11
}

函数将会返回container[i]的类型的返回值,且是引用返回。decltype如果作用于比名字更加复杂的左值表达式中,将总是返回一个引用,比如对int x, decltype((x))将返回int&, 而decltype(x)的推导结果却是int, 不带引用。这个区别只在C++14上面,自动推导返回值类型的时候比较重要,而在C++11中几乎不会用到。

非类型模板参数

https://blog.csdn.net/lanchunhui/article/details/49634077

类的非类型模板参数

类的非类型模板参数需要在实例化类的时候手工进行指定,而函数的非类型模板化参数则可以纯粹由编译器推导得出。非类型模板参数不允许类对象(enum除外)和浮点数,其余的是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
emplate<typename T, int MAXSIZE=20>
class Stack
{
public:
Stack():idx(0){}
bool empty() const { return idx == 0;}
bool full() const { return idx == MAXSIZE;}
void push(const T&);
void pop();
T& top();
const T& top() const;
private:
int idx;
T elems[MAXSIZE];
}

Stack<int, 10> stackSize10;
Stack<int> stackSize20; //使用了缺省参数

函数的非类型模板参数

形如下面的代码,不一样的是模板类型可以由推导得出,不一定需要显式传入。

1
2
3
4
5
6
7
8
template <typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
return N;
}

char helloStr[] = "hello";
arraySize(helloStr); //返回6

又如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
bool f(const T& a, const T& b)
{
std::cout << (a > b) << std::endl;
return a > b;
}

template <typename T>
class A{
public:
A(T a){
std::cout << "Constructor" << std::endl;
}
};

int main()
{
f<int>(0, 1); //正确,
f(0, 1); //正确,T会被正确推导
A<int> a(1); // 正确
A b(1); // 错误,编译不通过
}

C++泛型编程以及函数重载

  • enable_if_t
  • enable_if

SFINAE(Substitution Failure Is Not An Error)

1
2
3
4
5
6
7
8
9
10
11
12
long multiply(int i, int j) { return i * j; }

template <class T>
typename T::multiplication_result multiply(T t1, T t2)
{
return t1 * t2;
}

int main(void)
{
multiply(4, 5);
}

main 函数调用 multiply 会使编译器会尽可能去匹配所有候选函数,虽然第一个 multiply 函数明显是较优的匹配,但是为了得到一个最精确的匹配,编译器依然会尝试去匹配剩下的候选函数,此时就会去推导 第二个multiply 函数,中间在参数推导的过程中出现了一个无效的类型 int::multiplication_result ,但是因为 SFINAE 原则并不会报错。

待续

0%