constecpr和常量表达式1. 底层const和顶层const对于大部分对象被const修饰都是顶层const对于指针来说本身被const修饰*在const左边叫做顶层const指向对象被const修饰*在const右边叫做底层const对于引用被const修饰的是顶层const2. constexpr常量表达式是指值不会改变而且编译期就能确定结果的变量使用字面量、const表达式初始化的const变量都是常量表达式使用变量初始化的const变量不是constexpr用来修饰的变量必须是常量表达式同时也必须使用常量表达式初始化如果不是的话编译期报错constexpr也会让编译器对所修饰常量表达式进行优化提升运行时效率。比如将const变量放到寄存器当中取得时候直接从寄存器而不从内存当中去取constexpr也可以修饰指针const修饰的指针是指针本身即顶层constconstexprconstinta0;constexprconstchar*pstrhello world;constexpr函数constexpr在C11 - C14 - C20的演进过程C11返回值类型单一内部只支持一句return语句想要执行多个步骤只能递归C14函数体内可以有多个语句支持控制流循环、判断等语句支持返回值为自定义类型constexpr自定义类型以及STL容器如array对于[]的支持到C17支持定义局部变量C17支持if constexpr分支语句对于符合条件的编译器运算其他的丢弃支持作用在lambda表达式要求同对函数的要求捕获的值也必须是字面量constexpr位置在参数列表后返回类型定义前C20支持new delete但是必须在constexpr函数体中完成资源的释放包括使用STL容器内部有动态资源的话开始支持STL容器如vector以及一些接口如findsort支持try-catch语句但是并不是普通的运行时抛异常而是异常出现时直接编译不通过支持constexpr联合体外界可以访问其活跃变量支持mutable修饰constexpr类的成员变量以实现在其constexpr成员函数中进行修改该成员变量支持constexpr虚函数constexpr普通函数constexpr也可以修饰函数通过这种方式修饰的函数参数、返回值都只能是字面量调用的时候也必须使用字面量或者常量表达式传参。constexpr修饰的函数如果符合要求调用的话那么就不会在运行时得到结果比inline内联函数在运行时省去的工作更多直接在编译时去完成函数结果的运算虽然会导致编译时间变长如果不符合调用要求的话就当作普通函数调用constexprintfib(inta){returna1?1:afib(a-1);}constexprintretfib(100);intn100;intretfib(n);constexpr构造函数constexpr不能修饰自定义类型除非该类型的构造函数被constexpr修饰同时该自定义类型有以下要求所有成员变量都是字面量并且都在初始化列表中初始化构造函数参数都是字面量如果没有按照要求传参那么就是在运行期进行的普通构造constexpr成员函数当constexpr修饰的成员函数所在的自定义类型的构造函数是被constexpt修饰时并且通过constexpr修饰的自定义类型调用就可以同样在编译器就完成对自定义类型的构建和其constexpr成员函数的执行其实使用和constexpr修饰的普通函数类似没有符合要求同样是按照普通成员函数执行constexpr模板函数其实就是constexpr普通函数加上了模板这一层一起在编译期完成工作classDate{public:constexprDate(intyear,intmonth,intday):_year(year),_month(month),_day(day){}constexprintgetYear()const{return_year;}private:int_year;int_month;int_day;};templateclassTconstexprintadd(T t1,T t2){returnt1t2;}intmain(){constexprDated(1,2,3);std::coutd.getYear();constexprintret1addint(1,2);return0;}constevalconstexpr修饰的函数既可能在编译时求值也可能在运行时求值consteval修饰的函数要求必须在编译时求值即满足constexpr修饰函数时须满足的条件否则编译报错constevalintfunc(inta,intb){returnab;}constexprintafunc(1,2);constinitconstinit作用于变量要求必须在编译时初始化但是在初始化后并不作为常量而是可以在运行时被修改constinit可以作用于静态存储区和线程存储期的变量例如全局变量、static变量、thread_local变量在各个线程内都存在一份的变量不能作用于自动存储期的变量例如函数的局部变量主函数中的非全局、非静态变量也算constexprintfunc(inta,intb){returnab;}constevalintfunc2(inta,intb){returna-b;}constinitintafunc(1,2);constinitintbfunc2(1,2);intmain(){constinitstaticintc1;return0;}constinit还可以作用在类上保证其在编译时初始化classA{public:constexprA(inta):a(a){}private:inta};constinitAa(1);intmain(){...}constinit还可以解决多个cpp文件中全局变量、静态变量初始化顺序的问题例如以下代码//a.cppinta10;//全局//b.cppexterninta;intba;这种方法就可能出现b在a之前初始化的问题所以使用constinit就能使两者一定在编译器都完成初始化constinitinta10;externconstinitinta;constinitintba;autoauto用来自动推到类型但是他有以下四点值得注意auto不能推导出引用想要使用引用必须显式指定。因为直接把引用对象交给auto的话那么auto最后拿到的实际还是被引用的对象那么就不会初始化出引用类型inta1;intraa;autocra;//c的类型是int具有const属性的对象去初始化auto对象时顶层const会被抛弃修饰本身的const底层const会被保留constinta10;autoba;//b的类型是intconstint*paa;autocpa;// c的类型是const int*使用auto去显式推导引用类型时顶层const会被保留constinta10;autoba;// const intconstint*constpaa;autocpa;// const int* constauto为万能引用传左值为左值引用传右值为右值引用const属性保留inta10;constintca;autor1a;//intautor2c;//const intautor3std::move(a);//intautor4std::move(c);//const int尾置返回类型和decltype尾置返回类型是在参数列表之后通过箭头指定返回类型表达式与auto配合使用在C11主要是为了解决auto不能直接作为返回类型的问题不过之后的标准放开了这个限制所以尾插表达式也就没那么重要了decltype是可以返回变量表达式的类型而且不像auto一样会对类型做处理而是给什么类型最后的结果就是什么类型也可以通过函数调用表达式的方式去使用函数调用返回值的类型但是这个过程中并不会真正去调用函数decltype对与引用有两种特殊的处理inta10;int*paa;decltype((a))ba;decltype(*pa)ca;b和c的类型都是intauto 必须通过初始化值来完成类型推导这一点对于成员变量是很不方便的但是decltype就解决了这个问题templatetypenameTclassIt{public:voidfunc(Tcontainer){_itcontainer.begin();}private:decltype(T().begin())_it;};C14中结合auto自动类型推导和decltype精准的类型判断可以使用decltype(auto)的方式自动精确推导inta10;constintraa;decltype(auto)bra;//b的类型是const intusing typedeftypedefvoid(*)(int,int)func;usingfunc1void(*)(int,int);templateclassTusingunordered_mapstring,Tcon;typedef和using都用来重定义类型但是两者有用法和功能上的区别。typedef采取先是原类型再是重定义类型的顺序或者嵌套比如函数指针的定义using采取先是重定义类型再是原类型的方式using可以完成带模板参数的类型重定义强类型枚举enumColor{RED,BLUE,YELLOW};//不能指定底层类型enumclassGender:uint8_t{MALE,FEMALE};intmain(){intcol2RED;//污染作用域、自动隐式类型转换Gender maleGender::MALE;}强枚举类型是对普通enum的优化写为enum class或者enum struct强枚举类型可以保证只能通过指定类域的方式访问能够避免隐式类型转换为int也能够通过:type的方式指定底层类型static_assertstatic_assert(sizeof(void*)8,the platform should be 64-bit);static_assert(sizeof(int)4,the int type should has 4 bytes)static_assert是用来做编译时断言如果不通过则编译报错与之相对的是assert它是在运行时报错并终止运行static_assert判断的式子必须是常量表达式即在编译期间可以确定结果tuple可以理解为支持任意多个类型的pairC17之后还支持了自动类型识别不需要显式指定类型tupleint,double,stringt1{1,1.1,1.11};autot2make_tuple(2,2.2,2.22);根据下标获取tuple元素intaget0(t1);doublebget1(t2);string strget2(t1);tuple的解包可以通过tie但是C17之后可以使用结构化绑定更加方便std::tuplet1(1,1.1,1.11);inta;doubleb;string str;std::tie(a,b,str)t1;auto[val1,val2,val3]t1;模板元编程利用模板的递归实例化等等语法规则在编译器就完成一部分结果的运行获取常用类型变量或者静态成员变量来完成//判断两个类型是否相同templateclassT,classUstructis_same{constexprstaticconstboolvaluefalse;};templateclassTstructis_sameT,T{constexprstaticconstboolvaluetrue;};//去掉consttemplateclassTstructremove_const{usingtypeT;};templateclassTstructremove_constconstT{usingtypeT;};类型萃取其实上面的例子中已经是简单的类型萃取了不过在STL中也给出了实现C17后为了简化书写用_v_t来代替::value::type进行基础类型判断std::coutstd::is_voidvoid::valuestd::endl;std::coutstd::is_integralint::valuestd::endl;std::coutstd::is_floating_pointfloat::valuestd::endl;std::coutstd::is_pointerint*::valuestd::endl;std::coutstd::is_referenceint::valuestd::endl;std::coutstd::is_constconstint::valuestd::endl;进行复合类型检查std::coutstd::is_function_vvoid()std::endl;//is function?std::coutstd::is_member_object_pointer_vint(Foo::*)std::endl;//is objects member pointerstd::coutstd::is_compound_vstd::stringstd::endl;//is fundamental type进行关系检查std::coutstd::is_sameint,int32_t::valuestd::endl;std::coutstd::is_base_of_vBase,Derivestd::endl;std::coutstd::is_convertiblechar*,std::string::valuestd::endl;//前者能够转化为后者类型修改intn10;constintcn10;std::add_constint::type a1;std::add_pointer_tintpan;std::add_lvalue_referenceint::type rnn;std::remove_constdecltype(cn)::type b10;std::remove_pointer_tint*c10;std::remove_referenceint::type d10;条件类型选择std::coutstd::is_same_vint,std::conditional_ttrue,int,floatstd::endl;std::coutstd::is_same_vfloat,std::conditional_tfalse,int,floatstd::endl;模板类型推导templateclassF,class...Argusinginvoke_result_tstd::invoke_result_tF,Arg...()SFINAESFINAE并不是单词而是substitute failure is not an error当模板匹配失败的时候并不会直接编译报错而是寻找是否有其他合适的模板如果找到最后也没有那么就编译报错应用一函数重载templateclassTautofunc(T x)-decltype(x,void()){std::coutx is implementablestd::endl;}templateclassTautofunc(T x)-std::void_tdecltype(x.size()){std::coutx has member fucntion sizestd::endl;}voidfunc(...){std::coutfallback funcstd::endl;}在上面的decltype中会首先尝试执行第一个条件例如x是否可以x是否具有成员函数size() )当条件成功时decltype会继续往后执行逗号表达式并返回类型对象比如void对象当条件失败时会继续寻找其他模板都没有就会调用func(…)应用二enable_if在编译时启用或者禁用函数模板templateclassT//对int类型启用typenamestd::enable_if_tstd::is_integralT,intFoo(T t){returnt1;}templateclassT//对浮点数类型启用typenamestd::enable_if_tstd::is_floating_pointT,floatFoo(T t){returnt/2;}enable_if会在第一个模板返回true时给出第二个参数的类型不过有一个void类型的缺省值不写的话在上面的例子中就是返回void应用三模板类型检查templateclassK,classVstd::enable_if_tstd::is_integralKvoidfunc(K k){std::coutkstd::endl;}如果enable_if判断条件失败不返回类型的话V就会推导失败因为V的作用就是进行类型检查所以不写名称也可以模板元编程的优缺点优点编译时运算运行时零开销类型安全可以通过类型萃取和SFINAE等进行类型的控制高度抽象自由操作的空间大可以构建各种库缺点编译时间长代码难度大可读性也相对较低报错并不方便阅读编译期运算不方便调试
C++11、C++14、C++17、C++20新特性解析(一)
constecpr和常量表达式1. 底层const和顶层const对于大部分对象被const修饰都是顶层const对于指针来说本身被const修饰*在const左边叫做顶层const指向对象被const修饰*在const右边叫做底层const对于引用被const修饰的是顶层const2. constexpr常量表达式是指值不会改变而且编译期就能确定结果的变量使用字面量、const表达式初始化的const变量都是常量表达式使用变量初始化的const变量不是constexpr用来修饰的变量必须是常量表达式同时也必须使用常量表达式初始化如果不是的话编译期报错constexpr也会让编译器对所修饰常量表达式进行优化提升运行时效率。比如将const变量放到寄存器当中取得时候直接从寄存器而不从内存当中去取constexpr也可以修饰指针const修饰的指针是指针本身即顶层constconstexprconstinta0;constexprconstchar*pstrhello world;constexpr函数constexpr在C11 - C14 - C20的演进过程C11返回值类型单一内部只支持一句return语句想要执行多个步骤只能递归C14函数体内可以有多个语句支持控制流循环、判断等语句支持返回值为自定义类型constexpr自定义类型以及STL容器如array对于[]的支持到C17支持定义局部变量C17支持if constexpr分支语句对于符合条件的编译器运算其他的丢弃支持作用在lambda表达式要求同对函数的要求捕获的值也必须是字面量constexpr位置在参数列表后返回类型定义前C20支持new delete但是必须在constexpr函数体中完成资源的释放包括使用STL容器内部有动态资源的话开始支持STL容器如vector以及一些接口如findsort支持try-catch语句但是并不是普通的运行时抛异常而是异常出现时直接编译不通过支持constexpr联合体外界可以访问其活跃变量支持mutable修饰constexpr类的成员变量以实现在其constexpr成员函数中进行修改该成员变量支持constexpr虚函数constexpr普通函数constexpr也可以修饰函数通过这种方式修饰的函数参数、返回值都只能是字面量调用的时候也必须使用字面量或者常量表达式传参。constexpr修饰的函数如果符合要求调用的话那么就不会在运行时得到结果比inline内联函数在运行时省去的工作更多直接在编译时去完成函数结果的运算虽然会导致编译时间变长如果不符合调用要求的话就当作普通函数调用constexprintfib(inta){returna1?1:afib(a-1);}constexprintretfib(100);intn100;intretfib(n);constexpr构造函数constexpr不能修饰自定义类型除非该类型的构造函数被constexpr修饰同时该自定义类型有以下要求所有成员变量都是字面量并且都在初始化列表中初始化构造函数参数都是字面量如果没有按照要求传参那么就是在运行期进行的普通构造constexpr成员函数当constexpr修饰的成员函数所在的自定义类型的构造函数是被constexpt修饰时并且通过constexpr修饰的自定义类型调用就可以同样在编译器就完成对自定义类型的构建和其constexpr成员函数的执行其实使用和constexpr修饰的普通函数类似没有符合要求同样是按照普通成员函数执行constexpr模板函数其实就是constexpr普通函数加上了模板这一层一起在编译期完成工作classDate{public:constexprDate(intyear,intmonth,intday):_year(year),_month(month),_day(day){}constexprintgetYear()const{return_year;}private:int_year;int_month;int_day;};templateclassTconstexprintadd(T t1,T t2){returnt1t2;}intmain(){constexprDated(1,2,3);std::coutd.getYear();constexprintret1addint(1,2);return0;}constevalconstexpr修饰的函数既可能在编译时求值也可能在运行时求值consteval修饰的函数要求必须在编译时求值即满足constexpr修饰函数时须满足的条件否则编译报错constevalintfunc(inta,intb){returnab;}constexprintafunc(1,2);constinitconstinit作用于变量要求必须在编译时初始化但是在初始化后并不作为常量而是可以在运行时被修改constinit可以作用于静态存储区和线程存储期的变量例如全局变量、static变量、thread_local变量在各个线程内都存在一份的变量不能作用于自动存储期的变量例如函数的局部变量主函数中的非全局、非静态变量也算constexprintfunc(inta,intb){returnab;}constevalintfunc2(inta,intb){returna-b;}constinitintafunc(1,2);constinitintbfunc2(1,2);intmain(){constinitstaticintc1;return0;}constinit还可以作用在类上保证其在编译时初始化classA{public:constexprA(inta):a(a){}private:inta};constinitAa(1);intmain(){...}constinit还可以解决多个cpp文件中全局变量、静态变量初始化顺序的问题例如以下代码//a.cppinta10;//全局//b.cppexterninta;intba;这种方法就可能出现b在a之前初始化的问题所以使用constinit就能使两者一定在编译器都完成初始化constinitinta10;externconstinitinta;constinitintba;autoauto用来自动推到类型但是他有以下四点值得注意auto不能推导出引用想要使用引用必须显式指定。因为直接把引用对象交给auto的话那么auto最后拿到的实际还是被引用的对象那么就不会初始化出引用类型inta1;intraa;autocra;//c的类型是int具有const属性的对象去初始化auto对象时顶层const会被抛弃修饰本身的const底层const会被保留constinta10;autoba;//b的类型是intconstint*paa;autocpa;// c的类型是const int*使用auto去显式推导引用类型时顶层const会被保留constinta10;autoba;// const intconstint*constpaa;autocpa;// const int* constauto为万能引用传左值为左值引用传右值为右值引用const属性保留inta10;constintca;autor1a;//intautor2c;//const intautor3std::move(a);//intautor4std::move(c);//const int尾置返回类型和decltype尾置返回类型是在参数列表之后通过箭头指定返回类型表达式与auto配合使用在C11主要是为了解决auto不能直接作为返回类型的问题不过之后的标准放开了这个限制所以尾插表达式也就没那么重要了decltype是可以返回变量表达式的类型而且不像auto一样会对类型做处理而是给什么类型最后的结果就是什么类型也可以通过函数调用表达式的方式去使用函数调用返回值的类型但是这个过程中并不会真正去调用函数decltype对与引用有两种特殊的处理inta10;int*paa;decltype((a))ba;decltype(*pa)ca;b和c的类型都是intauto 必须通过初始化值来完成类型推导这一点对于成员变量是很不方便的但是decltype就解决了这个问题templatetypenameTclassIt{public:voidfunc(Tcontainer){_itcontainer.begin();}private:decltype(T().begin())_it;};C14中结合auto自动类型推导和decltype精准的类型判断可以使用decltype(auto)的方式自动精确推导inta10;constintraa;decltype(auto)bra;//b的类型是const intusing typedeftypedefvoid(*)(int,int)func;usingfunc1void(*)(int,int);templateclassTusingunordered_mapstring,Tcon;typedef和using都用来重定义类型但是两者有用法和功能上的区别。typedef采取先是原类型再是重定义类型的顺序或者嵌套比如函数指针的定义using采取先是重定义类型再是原类型的方式using可以完成带模板参数的类型重定义强类型枚举enumColor{RED,BLUE,YELLOW};//不能指定底层类型enumclassGender:uint8_t{MALE,FEMALE};intmain(){intcol2RED;//污染作用域、自动隐式类型转换Gender maleGender::MALE;}强枚举类型是对普通enum的优化写为enum class或者enum struct强枚举类型可以保证只能通过指定类域的方式访问能够避免隐式类型转换为int也能够通过:type的方式指定底层类型static_assertstatic_assert(sizeof(void*)8,the platform should be 64-bit);static_assert(sizeof(int)4,the int type should has 4 bytes)static_assert是用来做编译时断言如果不通过则编译报错与之相对的是assert它是在运行时报错并终止运行static_assert判断的式子必须是常量表达式即在编译期间可以确定结果tuple可以理解为支持任意多个类型的pairC17之后还支持了自动类型识别不需要显式指定类型tupleint,double,stringt1{1,1.1,1.11};autot2make_tuple(2,2.2,2.22);根据下标获取tuple元素intaget0(t1);doublebget1(t2);string strget2(t1);tuple的解包可以通过tie但是C17之后可以使用结构化绑定更加方便std::tuplet1(1,1.1,1.11);inta;doubleb;string str;std::tie(a,b,str)t1;auto[val1,val2,val3]t1;模板元编程利用模板的递归实例化等等语法规则在编译器就完成一部分结果的运行获取常用类型变量或者静态成员变量来完成//判断两个类型是否相同templateclassT,classUstructis_same{constexprstaticconstboolvaluefalse;};templateclassTstructis_sameT,T{constexprstaticconstboolvaluetrue;};//去掉consttemplateclassTstructremove_const{usingtypeT;};templateclassTstructremove_constconstT{usingtypeT;};类型萃取其实上面的例子中已经是简单的类型萃取了不过在STL中也给出了实现C17后为了简化书写用_v_t来代替::value::type进行基础类型判断std::coutstd::is_voidvoid::valuestd::endl;std::coutstd::is_integralint::valuestd::endl;std::coutstd::is_floating_pointfloat::valuestd::endl;std::coutstd::is_pointerint*::valuestd::endl;std::coutstd::is_referenceint::valuestd::endl;std::coutstd::is_constconstint::valuestd::endl;进行复合类型检查std::coutstd::is_function_vvoid()std::endl;//is function?std::coutstd::is_member_object_pointer_vint(Foo::*)std::endl;//is objects member pointerstd::coutstd::is_compound_vstd::stringstd::endl;//is fundamental type进行关系检查std::coutstd::is_sameint,int32_t::valuestd::endl;std::coutstd::is_base_of_vBase,Derivestd::endl;std::coutstd::is_convertiblechar*,std::string::valuestd::endl;//前者能够转化为后者类型修改intn10;constintcn10;std::add_constint::type a1;std::add_pointer_tintpan;std::add_lvalue_referenceint::type rnn;std::remove_constdecltype(cn)::type b10;std::remove_pointer_tint*c10;std::remove_referenceint::type d10;条件类型选择std::coutstd::is_same_vint,std::conditional_ttrue,int,floatstd::endl;std::coutstd::is_same_vfloat,std::conditional_tfalse,int,floatstd::endl;模板类型推导templateclassF,class...Argusinginvoke_result_tstd::invoke_result_tF,Arg...()SFINAESFINAE并不是单词而是substitute failure is not an error当模板匹配失败的时候并不会直接编译报错而是寻找是否有其他合适的模板如果找到最后也没有那么就编译报错应用一函数重载templateclassTautofunc(T x)-decltype(x,void()){std::coutx is implementablestd::endl;}templateclassTautofunc(T x)-std::void_tdecltype(x.size()){std::coutx has member fucntion sizestd::endl;}voidfunc(...){std::coutfallback funcstd::endl;}在上面的decltype中会首先尝试执行第一个条件例如x是否可以x是否具有成员函数size() )当条件成功时decltype会继续往后执行逗号表达式并返回类型对象比如void对象当条件失败时会继续寻找其他模板都没有就会调用func(…)应用二enable_if在编译时启用或者禁用函数模板templateclassT//对int类型启用typenamestd::enable_if_tstd::is_integralT,intFoo(T t){returnt1;}templateclassT//对浮点数类型启用typenamestd::enable_if_tstd::is_floating_pointT,floatFoo(T t){returnt/2;}enable_if会在第一个模板返回true时给出第二个参数的类型不过有一个void类型的缺省值不写的话在上面的例子中就是返回void应用三模板类型检查templateclassK,classVstd::enable_if_tstd::is_integralKvoidfunc(K k){std::coutkstd::endl;}如果enable_if判断条件失败不返回类型的话V就会推导失败因为V的作用就是进行类型检查所以不写名称也可以模板元编程的优缺点优点编译时运算运行时零开销类型安全可以通过类型萃取和SFINAE等进行类型的控制高度抽象自由操作的空间大可以构建各种库缺点编译时间长代码难度大可读性也相对较低报错并不方便阅读编译期运算不方便调试