深入理解c++20 concepts
concepts在c20中被引入其作用是对模板参数进行约束极大地增强了c模板的功能。在c20之前如果希望获取类似的效果使用起来并不方便。没有concept时如何实现对模板参数进行约束static_assert我们可以使用static_assert去对模板类型T进行约束。如下所示12345678910111213#include type_traits#include iostreamtemplateclassTvoidtest(T a){static_assert(std::is_integralT());std::cout T is integral std::endl;}intmain(){test(10);testdouble(12.3);}但是该种方法不太好因为需要将static_assert嵌入到函数的内部这意味着即使类型不对模板还是成功的匹配上了只是在模板函数内部展开时出现编译错误。SFINAESFINAE是Substitution Failure Is Not An Error的缩写翻译过来的意思是替换失败并不是一个错误。SFINAE是模板元编程中常见的一种技巧如果模板实例化后的某个模板函数模板类对该调用无效那么将继续寻找其他重载决议而不是引发一个编译错误。因此一句话概括SFINAE就是模板匹配过程中会尝试各个重载模板直到所有模板都匹配失败才会认为是真正的错误。例如下面这个经典的例子123456789101112structTest {typedefintfoo;};templatetypenameTvoidf(typenameT::foo) {}// Definition #1templatetypenameTvoidf(T) {}// Definition #2intmain() {fTest(10);// Call #1.fint(10);// Call #2. Without error (even though there is no int::foo)// thanks to SFINAE.}fTest(10)最终将使用到第一个模板定义 而fint(10)最终将使用到第二个模板定义。SFINAE 原则最初是应用于上述的模板编译过程。后来被C开发者发现可以用于做编译期的决策配合sizeof可以进行一些判断类是否定义了某个内嵌类型、类是否包含某个成员函数等。例如STL中迭代器中的has_iterator_category。123456789templatetypenameTstructhas_iterator_category {structtwo {chara;charb; };templatetypenameCstatictwo test(typenameC::iterator_category*);templatetypenamestaticchar test(...);staticconstboolvalue sizeof(testT(nullptr)) sizeof(two);};enable_ifenable_if的出现使得SFINAE使用上更加方便进一步扩展了上面has_xxxis_xxx的作用。而enable_if实现上也是使用了SFINAE。enable_if的定义简单 即当_Test是true时将不会有type的类型定义而当_Test是false时将会有type的类型定义。1234567// STRUCT TEMPLATE enable_iftemplatebool_Test,class_Ty voidstructenable_if {};// no member type when !_Testtemplateclass_Tystructenable_iftrue, _Ty {// type is _Ty for _Testusingtype _Ty;};下面是利用enable_if去实现SFINAE的方式。12345678910111213141516171819202122#include type_traits#include iostreamtemplatetypenameTtypenamestd::enable_ifstd::is_integralT::value, T::typetest(T value){std::coutT is intergalstd::endl;returnvalue;}templatetypenameTtypenamestd::enable_if!std::is_integralT::value, T::typetest(T value){std::coutT is not intergalstd::endl;returnvalue;}intmain(){test(100);test(a);test(100.1);}可以看到SFINAE的主要实现了is_xxx和has_xxx的语义但是其语法并不简单对使用者有较高要求并且即便写正确了可读性也相对较差。有了concept之后如何使用声明concept声明一个concept的语法如下所示12templatetemplate-parameter-list concept concept-name constraint-expression;例如约束T是一个整形12templatetypenameTconcept integral std::is_integral_vT;也可以使用requires更加灵活地定义concept例如下面的例子要求T类型拥有一个名字叫做print的方法另外需要拥有一个toString方法并且返回值是string类型。