- constexpr if
- constexpr lambda
- fold expression
- void_t
- structureed binding
- std::apply std::invoke
- string_view
- parallel STL
- inline variable
来自boost库,比如variant,any,optional,filesystem等;
语法糖:if init、deduction guide、guaranteed copy Elision、template
fold exception
fold exception语义
fold expression有四种语义:
- unary right fold (pack op…)
- unary left fold (…op pack)
- binary right fold (pack op … op init)
- binary right fold (init pack op … op)
其中pack为变参,op为操作符。
一元左折叠(unary left fold)( ... op pack )一元左折叠 (... op E) 展开之后变为 ((E1 op E2) op ...) op En一元右折叠(unary right fold)( pack op ... )一元右折叠 (E op ...) 展开之后变为 E1 op (... op (En-1 op En))二元左折叠(binary left fold)( init op ... op pack )二元左折叠 (I op ... op E) 展开之后变为 (((I op E1) op E2) op ...) op En二元右折叠(binary right fold)( pack op ... op init )二元右折叠 (E op ... op I) 展开之后变为 E1 op (... op (EN−1 op (EN op I)))
unary right fold
从表达式右边开始fold,看它是left fold还是right fold我们可以根据参数包…所在的位置来判断,当参数包…在操作符右边的时候就是right fold,在左边的时候就是left fold。
template<typename... Args>auto add_val(Args&&... args){return (args + ...);}auto t = add_val(1,2,3,4); //10
unary left fold
对于+这种满足交换律的操作符来说,left fold和right fold是一样的,比如上面的例子你也可以写成left fold。
template<typename... Args>auto add_val(Args&&... args){return (... + args);}auto t = add_val(1,2,3,4); //10
对于不满足交换律的操作符来说就要注意了,比如减法,下面的right fold和left fold的结果就不一样。
template<typename... Args>auto sub_val_right(Args&&... args){return (args - ...);}template<typename... Args>auto sub_val_left(Args&&... args){return (... - args);}auto t = sub_val_right(2,3,4); //(2-(3-4)) = 3auto t1 = sub_val_left(2,3,4); //((2-3)-4) = -5
binary right fold
二元fold的语义和一元fold的语义是相同的,参数包…在左即二元左折叠,参数包…在右即右折叠。下面看一个二元右折叠的例子。
template<typename... Args>auto sub_one_left(Args&&... args){return (1 - ... - args);}auto t = sub_one_right(2,3,4);//(2-(3-(4-1))) = 2
binary left fold
template<typename... Args>auto sub_one_right(Args&&... args){return (args - ... - 1);}auto t = sub_one_left(2,3,4);// (((1-2)-3)-4) = -8
comma fold
在C++17之前,我们经常使用逗号表达式结合列表初始化的方式对参数包进行展开,比如像下面这个例子:
template<typename T>void print_arg(T t){std::cout << t << std::endl;}template<typename... Args>void print2(Args... args){int a[] = { (print_arg(args), 0)... };//或者//std::initializer_list<int>{(print_arg(args), 0)...};}
这种写法比较繁琐,用fold expression就会变得很简单了。
//unary right foldtemplate<typename... Args>void print3(Args... args){(print_arg(args), ...);}//unary left foldtemplate<typename... Args>void print3(Args... args){(..., print_arg(args));}
unary right fold和unary left fold,对于comma来说两种写法是一样的,参数都是从左至右传入print_arg函数。当然,我们也可以通过binary fold实现:
template<typename ...Args>void printer(Args&&... args){(std::cout << ... << args) << '\n';}
注意,下面的写法是不合法的,根据binary fold的语法,参数包…必须在操作符中间。
template<typename ...Args>void printer(Args&&... args){(std::cout << args << ...) << '\n';}
constexpr if
constexpr标记一个表达式或一个函数的返回结果是编译期常量,它保证函数会在编译期执行。相比模版来说,实现编译期循环或递归,C++17中的constexpr if会让代码变得更简洁易懂。比如实现一个编译期整数加法:
template<int N>constexpr int sum(){return N;}template <int N, int N2, int... Ns>constexpr int sum(){return N + sum<N2, Ns...>();}
C++17之前你可能需要像上面这样写,但是现在你可以写更简洁的代码了。
template <int N, int... Ns>constexpr auto sum17(){if constexpr (sizeof...(Ns) == 0)return N;elsereturn N + sum17<Ns...>();}
当然,你也可以用C++17的fold expression:
template<typename ...Args>constexpr int sum(Args... args) {return (0 + ... + args);}
constexpr还可以用来消除enable_if了,对于讨厌写一长串enable_if的人来说会非常开心。比如我需要根据类型来选择函数的时候:
template<typename T>std::enable_if_t<std::is_integral<T>::value, std::string> to_str(T t){return std::to_string(t);}template<typename T>std::enable_if_t<!std::is_integral<T>::value, std::string> to_str(T t){return t;}
经常不得不分开几个函数来写,还需要写长长的enable_if,比较繁琐,通过if constexpr可以消除enable_if了。
template<typename T>auto to_str17(T t){if constexpr(std::is_integral<T>::value)return std::to_string(t);elsereturn t;}
constexpr if让C++的模版具备if-else if-else功能了,是不是很酷,C++程序员的好日子来了。
不过需要注意的是下面这种写法是有问题的。
template<typename T>auto to_str17(T t){if constexpr(std::is_integral<T>::value)return std::to_string(t);return t;}
这个代码把else去掉了,当输入如果是非数字类型时代码可以编译过,以为if constexpr在模版实例化的时候会丢弃不满足条件的部分,因此函数体中的前两行代码将失效,只有最后一句有效。当输入的为数字的时候就会产生编译错误了,因为if constexpr满足条件了,这时候就会有两个return了,就会导致编译错误。
constexpr if还可以用来替换#ifdef宏,看下面的例子
enum class OS { Linux, Mac, Windows };//Translate the macros to C++ at a single point in the application#ifdef __linux__constexpr OS the_os = OS::Linux;#elif __APPLE__constexpr OS the_os = OS::Mac;#elif __WIN32constexpr OS the_os = OS::Windows;#endifvoid do_something() {//do something generalif constexpr (the_os == OS::Linux) {//do something Linuxy}else if constexpr (the_os == OS::Mac) {//do something Appley}else if constexpr (the_os == OS::Windows) {//do something Windowsy}//do something general}
代码变得更清爽了,再也不需要像以前一样写#ifdef那样难看的代码块了。
constexpr lambda
constexpr lambda其实很简单,它的意思就是可以在constexpr 函数中用lambda表达式了,这在C++17之前是不允许的。这样使用constexpr函数和普通函数没多大区别了,使用起来非常舒服。下面是constexpr lambda的例子:
template <typename I>constexpr auto func(I i) {//use a lambda in constexpr contextreturn [i](auto j){ return i + j; };}
string_view
string_view的基本用法
C++17中的string_view是一个char数据的视图或者说引用,它并不拥有该数据,是为了避免拷贝,因此使用string_view可以用来做性能优化。你应该用string_view来代替const char和const string了。string_view的方法和string类似,用法很简单:
const char* data = "test";std::string_view str1(data, 4);std::cout<<str1.length()<<'\n'; //4if(data==str1)std::cout<<"ok"<<'\n';const std::string str2 = "test";std::string_view str3(str2, str2.size());
构造string_view的时候用char*和长度来构造,这个长度可以自由确定,它表示string_view希望引用的字符串的长度。因为它只是引用其他字符串,所以它不会分配内存,不会像string那样容易产生临时变量。我们通过一个测试程序来看看string_view如何来帮我们优化性能的。
using namespace std::literals;constexpr auto s = "it is a test"sv;auto str = "it is a test"s;constexpr int LEN = 1000000;boost::timer t;for (int i = 0; i < LEN; ++i) {constexpr auto s1 = s.substr(3);}std::cout<<t.elapsed()<<'\n';t.restart();for (int i = 0; i < LEN; ++i) {auto s2 = str.substr(3);}std::cout<<t.elapsed()<<'\n';//output0.0041970.231505
我们可以通过字面量””sv来初始化string_view。string_view的substr和string的substr相比,快了50多倍,根本原因是它不会分配内存。
string_view的生命周期
由于string_vew并不拥有锁引用的字符串,所以它也不会去关注被引用字符串的生命周期,用户在使用的时候需要注意,不要将一个临时变量给一个string_view,那样会导致string_view引用的内容也失效。
std::string_view str_v;{std::string temp = "test";str_v = {temp};}
这样的代码是有问题的,因为出了作用域之后,string_view引用的内容已经失效了。
