《C++20 概念与约束(1)—— SFINAE》 ●《C++20 概念与约束(2)—— 初识概念与约束》 《C++20 概念与约束(3)—— 约束的进阶用法》 |
1、概念
C++20 中引入新的编译期关键字 concept 用于创建概念。个人认为将其翻译为“构思”更为贴切。直接使用时,它更像一个只能用于模板的布尔类型关键字。
而如果用于模板中,他会将模板类型先带入自身,当自身条件为 true 才会实例化模板,否则该模板被弃置。因此该关键字几乎全用于 SFINAE 规则。
如上图, foo 将因为找不到匹配模板而报错。
因此只需要通过逻辑条件运算符将多条语句进行列举即可。
此外,概念还可用于简写模板之中,只需写在 auto 之前即可:
同样,第一个 foo 调用符合概念,而传入 char 类型违反概念,因此该行报错,找不到模板。
因此概念是通过约定模板需要满足的条件来使用 SFINAE 规则的关键字。
2、约束
C++20 中引入另一个关键字 requires。不同于 concept , requires 关键字可以使用户无需定义概念而对模板添加实例化的条件。当然不仅仅如此,个人觉得 requires 的用法特别混乱,没办法简单几句话概括其用法。
用英语单词来作对比,requires有动词和名词两种用法。在语法中也可以这么理解,主句的 requires 是动词,从句则是名词。
1、requires主句
requires 主句后跟布尔值类型的表达式,表示要求达成后续条件。因此,之前例子中的概念也可用 requires 主句将其写到同一个模板中。
此外,因为概念的表达式值也是布尔类型,因此以下写法也是合法的:
题外话,这时候还可以对比 bool 类型的常量模板,会发现布尔类型的常量模板和概念特别相似,只不过概念可以放到定义模板的尖括号中,而布尔常量不行:
总之,requires 主句只需要后续表达式值为 true 则允许实例化模板,否则该模板将被放弃匹配。
2、requires从句
requires 从句与主句不同,它后面必须跟花括号,括号中的表达式必须在编译期能够确定。并且,requires 从句的结果只有语法不允许和 true 两种,也就是说,在非模板中使用,只要编译能通过,它的值一定是 true ,它更像是条件的集合。根据此特性,requires 从句往往用于模板中以匹配 SFINAE 规则。
上图中两个 requires 第一个必然是主句,用于要求后续条件符合。第二个 requires 便是从句,它要求 T::type1 和 T::type2 是合法语句才会返回 true 。 X 类型不符合该规则,该模板被弃置, foo<X> 无匹配模板。而 Y 符合该条件, foo<Y> 将被实例化。
此外,从句的 requires 还允许后续紧跟一对括号用于类型的编译期的实例化。编译期的实例化并不会被真正实例化,只是用于检查实例化后的对象是否符合某种特性。
可见该约束要求类型支持解引用和同类型相加的操作, X 类型不符合该规则,只有 foo<Y> 能被正确实例化。
3、再说主句与从句
先提个毫无意义的小 tip ,requires 的主句和从句可以互相无限嵌套。接下来的内容不考虑嵌套约束的情况,仅讨论 requires 子句中使用普通表达式的情况。
仅仅使用普通表达式作为子句的约束条件,子句只要有返回值,只有可能是 true 这一种结果。在非模板中,如果 requires 子句中的表达式非法是直接报错,非模板环境不存在 SFINAE 机制。
如上图,报错信息与直接在 main 函数中使用该表达式的报错没有任何不同。
不可认为 requires 主句是因为从句不符合语法而返回 false 而启动 SFINAE 机制。之所以模板中的约束使得模板不被实例化是因为子句条件不符合而被抛弃,类似于以下模板中第二个模板默认类型形参的不符合。