decltype(exp)
推导规则:exp
为 标识符、类访问表达式class::member,decltype(exp)
和exp
类型一致exp
为 函数调用,decltype(exp)
和返回值类型一致exp
的返回结果为 左值,decltype(exp)
是exp
的左值引用:int x; // decltype((x)) 为 int&
示例:
template <typename T, typename U> auto add(T t, U u) -> decltype(t + u) { return t + u; } /*****************************************/ int& foo(int& i); float foo(float& f); template <typename T> auto func(T& val) -> decltype(foo(val)) { return foo(val); }
>>
,防止二义性可加括号using
可定义<模板>别名 alias template类模板即使都有默认模板参数也要加<>
函数模板可不加<>,且没有默认参数必须写在最后的限制(若指定模板参数 直接从左到右填充),指定参数 优于 自动推理 优于 默认参数。
{}
前有无=不影响初始化类型type var{*}
等价于type var={*}
,除非 type var = type {*}
会在列表初始化匿名对象后,拷贝初始化var。
聚合类型定义 Aggregates
std::initializer_list
initializer_list保存了{}中对象T的数组(size为两个地址长:begin和end指针),长度任意,只能整体初始化或赋值,有size()/begin()/end()三个接口。
使用 __for_begin
(首迭代器) __for_end
(尾迭代器) __for_range
(迭代容器的引用) 三个变量支持范围for。若自定义类欲支持范围for,需实现begin()和end()。
for (const auto& val : exp)
exp只在最开始执行一次,遍历基于迭代器实现,有可能出现迭代器失效,故尽量不要修改迭代的容器。
std::function
和bind
统一可调度对象的定义,绑定可调用对象与其参数可调用对象 callable objects:
std::placeholders::_1
[capture] (params) opt -> ret {body;}
返回值类型可不声明,由body中的return语句自动推导。
若按值捕获外部变量[=],值在lambda声明时即固定
const ret_type operator()(const struct {...} * const __closure);
可指明opt选项为mutable
ret_type operator()(struct {...} * const __closure);
或 按引用捕获外部变量[&]。
lambda可理解为就地定义仿函数的语法糖,捕获的外部变量即为成员变量。
没有捕获变量的lambda可转化为函数指针(没捕获就不需要this指针)。
增强版std::pair。实现涉及模板元编程。
左值右值将亡值概念
An lvalue has an address that your program can access. Examples of lvalue expressions include variable names, including const variables, array elements, function calls that return an lvalue reference, bit-fields, unions, and class members.
A prvalue expression has no address that is accessible by your program. Examples of prvalue expressions include literals, function calls that return a non-reference type, and temporary objects that are created during expression evaluation but accessible only by the compiler.
An xvalue expression has an address that no longer accessible by your program but can be used to initialize an rvalue reference, which provides access to the expression. Examples include function calls that return an rvalue reference, and the array subscript, member and pointer to member expressions where the array or object is an rvalue reference.
右值引用的类型可以是左值也可以是右值(std::move)
编译器会将已命名的右值引用视为左值(如函数传入的参数为 右值引用+其名称(相当于命名),确实会在栈上移动构造value,给分配空间(见2.3 可用std::forward
保持左值、右值特征))
引用折叠规则:右值引用叠加到右值引用还是右值引用、其它叠加方式变成左值引用。
T&&
或auto&&
(不加const)这种发生类型推断的情况,叫做universal reference,可变成左值引用或右值引用,必须初始化。
使用std::move将左值转换为右值引用,从而可通过移动构造 转移资源控制权。
完美转发 perfect forwarding:指在函数模板中,完全依照模板的参数类型(保持左值、右值特征)将参数传递给函数模板中调用的另一个参数。
可用 universal reference + 完美转发 + 可变模板参数 实现通用函数包装器。
push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个临时元素)
emplace_back() 在实现时,则是直接在容器尾部创建这个元素(需直接在括号里写构造参数),省去了拷贝或移动元素的过程。
unordered_(multi)map/unordered_(multi)set 使用hash表存储元素(map/set使用红黑树)空间换时间。对自定义类型需要实现hash函数和比较函数。
type_traits的类型选择功能可降低程序的圈复杂度,提高可维护性。
定义编译期常量
继承 std::integral_constant
类型,是下列类的类型(?)
类型判断的type_traits
在头文件<type_traits>中:is_void is_enum is_lvalue_reference is_const is_unsigned is_move_constructible...
判断两个类型之间关系的type_traits Type relationships
std::integral_constantis_base_of is_convertible ...
类型转换的traits Type modifications
remove(add)_cv/const/volatile
std::decay 移除引用和cv or 数组退化为元素 or 函数退化为函数指针
以一段开源代码为例:
// get specific T value from a map<string, any>, only if type 'T' matches 'any' // template <class T, typename U = std::remove_cv<std::remove_reference<T>>> template <class T, typename U = std::decay<T>> static const U &GetValue(const std::shared_ptr<std::map<std::string, std::any>> &data, const std::string &key) { static const U empty_result{}; if (data == nullptr) { return empty_result; } auto iter = data->find(key); if (iter == data->end()) { return empty_result; } const std::any &value = iter->second; if (value.type() != typeid(U)) { return empty_result; } return std::any_cast<const U &>(value); }
#include <typeinfo> #include <cxxabi.h> #include <type_traits> abi::__cxa_demangle(typeid(decay<decltype(func)>::type).name(), 0, 0, 0) // func为void()类型,加入decay变为void (*)()
std::conditional
在编译期根据一个判断式选择两个类型中的一个,类似于三元表达式
// 原型如下 template<bool B, class T, class F> struct conditional // 例如: using ret_type = std::conditional<std::is_integral<int>::value, long, doube>::type // ret_type为long
std::result_of
编译器获得可调用对象(见1.5)的返回类型
void func() { } // 下列 A B C D 类型均为 void using A = std::result_of<decltype(func)&()>::type; // decltype(func)&() 等价于 void (&())() // 下列result_of的参数 等价于 remove_reference<remove_cv<remove_reference<void (*)()>::type>::type using B = std::result_of<decltype(&func)()>::type; using C = std::result_of<decltype(func)*()>::type; using D = std::result_of<typename std::decay<decltype(func)>::type ()>::type;
std::enable_if
利用SFINAE(substitution-failure-is-not-an-error)实现编译期的模板类型限定,若完全找不到匹配则编译报错
可用于限定函数返回值、参数类型、
template<typename T> typename std::enable_if<std::is_arithmetic<T>::value, std::string>::type func(T t) { }; auto s1 = func(1); // T is int, return string type auto s2 = func(2.0); // T is double, return string type auto s3 = func("333"); // T is char*, compile error /********************************************************/ template<class T, class = typename std::enable_if<std::is_floating_point<T>::value>::type> T func(T t) { return t; } func(1.0f); // returns float (1.0f) func(1.0l); // returns long double (1.0l) func(1); // compile error /********************************************************/ template<class T> typename std::enable_if<std::is_same<T, std::string>::value, std::string>::type ToString(T& t) { return t; } template<class T> typename std::enable_if<std::is_arithmetic<T>::value, std::string>::type ToString(T& t) { return std::to_string(t); }
模板声明时在template和class后带上省略号...
省略号...的作用:
template<class... T> void func(T... args) { std::cout << sizeof...(args) << std::endl; // sizeof...() 可获得变参的个数 } func() // print 0 func(1, 2, "3"); // print 3 func(1, '2', 3.0f, "4"); // print 4
class Serializer { public: Serializer() = default; virtual ~Serializer() = default; /* * Code function call to generated code */ template <typename... PARAMETERS> void CodeFunction(const std::string &name, PARAMETERS... parameters) { code << name << "("; GenCode(parameters...); code << ");\n"; } template <typename T> Serializer &operator<<(T t) { code << t; return *this; } protected: std::ostringstream code; private: template <typename T, typename... REST> void GenCode(T t, REST... args) { GenCode(t); code << ", "; GenCode(args...); } // general type template <typename T> void GenCode(T t) { code << t; } // Convert pointer to string template <typename T> void GenCode(T *t) { if (t == nullptr) { code << "NULL"; } else { std::string name = MemoryAllocator::GetInstance()->GetRuntimeAddr(t, true); if (name.empty()) { exit(1); } code << name; } } // std::boolalpha converts bool to string literals {"true", "false"} instead of {1, 0} void GenCode(bool t) { code << std::boolalpha << t; } void GenCode(int8_t t) { code << std::to_string(t); } void GenCode(uint8_t t) { code << std::to_string(t); } void GenCode(decltype(nullptr) t) { code << "NULL"; } void GenCode(const char *t) { code << t; } void GenCode(LiteDataType t) { code << "(LiteDataType)" << t; } };
上段代码以跳入某个特化的版本为递归终止条件。
除了给出特化版本,也可以使用 type_traits + tuple 判断size展开参数包。
template<class T> void printArg(T t) { std::cout << t << std::endl; } template<class ...Args> void print(Args... args) { std::initializer_list<int>{(printArg(args), 0)...}; // 也可使用 lambda // 等价于创建了一个元素均为0的数组 // 但每个元素初始化前,由于存在逗号表达式,会先调用printArg,再返回0 }
(E op ...)
becomes (E1 op (... op (EN-1 op EN)))
(... op E)
becomes (((E1 op E2) op ...) op EN)
(E op ... op I)
becomes (E1 op (... op (EN−1 op (EN op I))))
(I op ... op E)
becomes ((((I op E1) op E2) op ...) op EN)
// 好用?
例1,如 3.2.1 中的示例
例2,某开源代码中,由不同构造函数 创建单例对象的工厂类
template <typename T> class Singleton { public: explicit Singleton(T &&) = delete; explicit Singleton(const T &) = delete; void operator=(const T &) = delete; // thread safety implement template <typename... _Args> static T &Instance(_Args&&... args) { static T instance(std::forward<_Args>(args)...); // 完美转发消除损耗 return instance; } protected: Singleton() = default; virtual ~Singleton() = default; };