Video: CppCon 2015:Marshall Clow “Type Traits - what are they and why should I use them?"
https://www.youtube.com/watch?v=VvbTP_k_Df4
如果你需要写关于不同类型的代码,而不是具体的类型,你可能需要了解Type Traits
4:47 C++多少种类型?14
- void, nullptr: 只有一个成员在这个type里面
- struct 实际上和class 是一样的,只是default access是public
- array vs pointer: array有一片物理区域,pointer仅仅是个地址。pointer可以为空
8:00 类型分类
08:21 type trait的定义
就是一个结构,里面包含模板的类型信息
例如,下面是个简单用法,输出true/false
#include <iostream>
#include <type_traits>
std::cout << std::is_floating_point<float>::value << ‘\n‘; // true
std::cout << std::is_floating_point<int>::value << ‘\n‘; // false
std::rank的用法,输出int
std::cout << std::rank<float>::value << ‘\n‘; // 0
std::cout << std::rank<int[1][1]>::value << ‘\n‘; // 2
std::cout << std::rank<int[2][3][4]>::value << ‘\n‘; // 3
有些可以输出type
std::remove_const<const int>::type --> int
std::remove_const<int>::type--> int
BTW:
- std::is_permulation 比较两个序列是否相同,顺序可以不同
- std::enable_if & SAFINAE
enable_if 的主要作用就是当某个 condition 成立时,enable_if可以提供某种类型。enable_if在标准库中通过结构体模板实现的
SFINAE 的的全称是 Substitution Failure Is Not An Error
SFINAE 是C++ 的一种语言属性,具体内容就是”从一组重载函数中删除模板实例化无效的函数”
19:20 这个视频认为SFINAE是C++里第二丑陋的缩略语。第一是RAII,因为根本就无法读出来。
下面的例子不是来自本视频
// long multiply(int i, int j) { return i * j; }
template <class T>
typename T::multiplication_result multiply(T t1, T t2)
{
return t1 * t2;
}
void test()
{
multiply(4, 5); // error
}
error C2893: Failed to specialize function template ‘T::multiplication_result multiply(T,T)‘
如果把第一行的注释去掉,这个error就会消失。这就是因为SFINAE原则,编译器会尝试所有的匹配,找到最合适的,其它不合适的有错也忽略。
19:48 SFINAE Example
int func(...) { return 0; }
template <typename T>
typename std::enable_if<std::is_integral<T>::value, int>::type
func(T val) {
return 1;
}
int func(float f) { return 2; }
void test()
{
std::cout << func(nullptr) << " ";
std::cout << func(2) << " ";
std::cout << func(2.f) << " ";
std::cout << func(2.0) << "\n";
}
输出: 0 1 2 2
第1行是C语言的不定参数的函数,经典例子是printf,和C++11的variadic arguments 不同。这一行匹配的优先级最低。
第4行,如果T不是整数类型时,会产生编译错误。根据SFINAE原则,使用其它的函数来匹配。
31:40
可以针对一些类型提供优化
32:00 优化的例子
每个人都觉得自己实现的vector会比stl的好。但事实上,STL里面的实现包含许多corner cases,非常复杂。
strong exception guarantee: 要么完全成功,要么出错抛异常,并且需要完全恢复的操作前的状态。例如,如果vector内存reserve不够,需要申请新的内存,并且把已经有的对象拷贝进入新内存。如果拷贝到一半出错,需要完全恢复这些操作,让vector恢复到之前的状态,并且抛出异常。
拷贝的代价很高(需要创建,构造,析构旧对象)。如果T支持move,可以利用move提高效率。但是如果move构造函数会抛出异常,既无法继续,又无法恢复!所以,必须要求no throw move-constructible! no throw意味着move永远都能成功。并不是所有的T都能实现no throw的,需要特别留意。
如果vector里面是int,或者T是个结构只包含基本类型。只要申请新内存,拷贝内存(never throw),释放旧内存,效率可言很高。有一种type trait叫做trivially copyable描述这种情况。
这些都不是必要的,没有这些也能正确实现。但是有了这些,可以优化得很好。
43:06 Questions
- 44:00 可以生成自己的type trait吗?可以。例如,std::is_floating_point,如果自己实现该怎么实现。针对float, double, long double这几种类型特化,返回true,默认返回false。有方法甚至可以实现这样的type trait: 一个结构是否包含一个member function叫做foo,并且参数是什么什么,返回什么。
- 47:05 对 SFINAE 的例子,一般有两种实现,一种是使用enable_if,另一种...。enable_if 有什么优势?这种方法更简单清晰,不容易出错。具体见视频。
- 50:16 是的,将来 concept 的引入会很大程度的改变现在的实现