目录
1.concept语法
1.1 替换typename
1.2 requires关键字
1.4 concept与auto
2.编译器支持
3.总结
C++20引入了concept(概念),是对模板参数(编译时评估)的一组约束。你可以将它们用于类模板和函数模板来控制函数重载和特化。一些优点包括:
对模版参数强制类型约束
提高代码可读性(替换了较长的SFINAE代码)
提供更友好的报错信息
通过限制可以使用的类型来防止意外的模板实例化
往期C++20的系列文章:
1.C++那些事之C++20协程开篇
2.盘点C++20模块那些事
注:本篇所有代码已更新于星球。
下面进入正文,以一个比较简单加法为例。
#includestruct Foo {}; template T Add(T a, T b) { return a + b; } int main() { std::cout << Add(1, 2) << std::endl; Foo f1, f2; std::cout << Add(f1, f2) << std::endl; return 0; }
对于Foo来说,是不支持加法的,于此同时也是不可以直接std::cout << ,因此在编译时报一大堆错误,包含operator<<与operator+,但这并不是我们期望的错误信息,我们比较期望的是编译器给我们最直观的错误信息,即:这个结构体能不能相加。
add.cc: In function 'int main()': add.cc:13:13: error: no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream'} and 'Foo') 13 | std::cout << Add(f1, f2) << std::endl; | ~~~~~~~~~ ^~ ~~~~~~~~~~~ | | | | | Foo | std::ostream {aka std::basic_ostream } In file included from /usr/local/Cellar/gcc/13.2.0/include/c++/13/iostream:41, from add.cc:1: /usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream:110:7: note: candidate: 'std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(__ostream_type& (*)(__ostream_type&)) [with _CharT = char; _Traits = std::char_traits ; __ostream_type = std::basic_ostream ]' 110 | operator<<(__ostream_type& (*__pf)(__ostream_type&)) /usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream:801:5: note: template argument deduction/substitution failed: /usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream: In substitution of 'template _Ostream&& std::operator<<(_Ostream&&, const _Tp&) [with _Ostream = std::basic_ostream &; _Tp = Foo]': add.cc:13:26: required from here /usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream:801:5: error: no type named 'type' in 'struct std::enable_if ' add.cc: In instantiation of 'T Add(T, T) [with T = Foo]': add.cc:13:19: required from here add.cc:7:12: error: no match for 'operator+' (operand types are 'Foo' and 'Foo') 7 | return a + b; | ~~^~~
当我们使用concept实现之后:
templateconcept Addable = requires(T a, T b) { a + b; }; template requires Addable T Add(T a, T b) { return a + b; }
便可以得到我们关心的编译错误:
add_concept.cc: In function 'int main()': add_concept.cc:17:19: error: no matching function for call to 'Add(Foo&, Foo&)' 17 | std::cout << Add(f1, f2) << std::endl; | ~~~^~~~~~~~ add_concept.cc:10:3: note: candidate: 'templaterequires Addable T Add(T, T)' 10 | T Add(T a, T b) { | ^~~ add_concept.cc:10:3: note: template argument deduction/substitution failed: add_concept.cc:10:3: note: constraints not satisfied add_concept.cc: In substitution of 'template requires Addable T Add(T, T) [with T = Foo]': add_concept.cc:17:19: required from here add_concept.cc:6:9: required for the satisfaction of 'Addable ' [with T = Foo] add_concept.cc:6:19: in requirements with 'T a', 'T b' [with T = Foo] add_concept.cc:6:42: note: the required expression '(a + b)' is invalid 6 | concept Addable = requires(T a, T b) { a + b; }; | ~~^~~
下面,我们来针对上面这个例子深入学习concept语法。
语法:
templateconcept concept-name = constraint-expression;
我们来对比一下实际的例子,Addable是concept-name,constraint-expression是requires(T a, T b) { a + b; }。
templateconcept Addable = requires(T a, T b) { a + b; };
使用方式为:
#include
这个concept可以放在多个地方,如下:
typename的位置
requires后面
auto前面
约束模版参数,替换typename。
// templatetypename->Addable template T Add(T a, T b) { return a + b; }
我们在函数模板中使用 requires 关键字。它可以访问我们的模板T是否是可以相加的,如果模板可以处理相加,它将返回 true。
requires可以放在模版中,也可以放在函数之后,但是不可以放在类之后。于此同时它有两个写法:
requires 条件
例如:
templaterequires Addable T Add(T a, T b) { ... }
requires 表达式
例如:
templateconcept Addable = requires(T a, T b) { a + b; };
函数:
templaterequires Addable T Add(T a, T b) { return a + b; }
类:
templaterequires Addable class Bar { public: T Add(T a, T b) { return a + b; } };
函数:
templateT Add(T a, T b) requires Addable { return a + b; }
对于类则不支持这种写法,会报错:error: expected unqualified-id before 'requires' 28 | requires Addable
templateclass Bar requires Addable { public: T Add(T a, T b) { return a + b; } };
以数据库当中的类型为例,数据库中有不同类型,我们将其划分为:null、binary、number等,我们想要对传递的类型执行打印操作,于是有了下面的示例:
#includeclass NumberType {}; class BaseBinaryType {}; class NullType {}; class FloatingPointType : public NumberType {}; class IntegerType : public NumberType {}; class BinaryType: public BaseBinaryType {}; template requires std::is_base_of_v || std::is_base_of_v void PrintValue(T v) {} int main() { PrintValue(FloatingPointType{}); PrintValue(NullType{}); return 0; }
对于requires我们可以使用||,上面示例中出现了NullType,它不满足requires,因此会编译出现:
concept_requires.cc:16:13: error: no matching function for call to 'PrintValue(NullType)' 16 | PrintValue(NullType{}); | ~~~~~~~~~~^~~~~~~~~~~~ concept_requires.cc:12:6: note: candidate: 'templaterequires (is_base_of_v ) || (is_base_of_v ) void PrintValue(T)' 12 | void PrintValue(T v) {} | ^~~~~~~~~~ concept_requires.cc:12:6: note: template argument deduction/substitution failed: concept_requires.cc:12:6: note: constraints not satisfied concept_requires.cc: In substitution of 'template requires (is_base_of_v ) || (is_base_of_v ) void PrintValue(T) [with T = NullType]': concept_requires.cc:16:13: required from here concept_requires.cc:12:6: required by the constraints of 'template requires (is_base_of_v ) || (is_base_of_v ) void PrintValue(T)' concept_requires.cc:11:45: note: no operand of the disjunction is satisfied 11 | requires std::is_base_of_v || std::is_base_of_v | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
当auto出现时,我们可以将其与concept一起使用,例如:
auto add(auto x, auto y) { return x + y; }
我们可以变为:
templateconcept Addable = requires(T a, T b) { a + b; }; auto add2(Addable auto x, Addable auto y) { return x + y; }
编译时会出现:
concept_auto.cc:17:20: error: no matching function for call to 'add2(Foo&, Foo&)' 17 | std::cout << add2(f1, f2) << std::endl; | ~~~~^~~~~~~~ concept_auto.cc:10:6: note: candidate: 'templaterequires (Addable) && (Addable) auto add2(auto:18, auto:19)' 10 | auto add2(Addable auto x, Addable auto y) { | ^~~~ concept_auto.cc:10:6: note: template argument deduction/substitution failed: concept_auto.cc:10:6: note: constraints not satisfied concept_auto.cc: In substitution of 'template requires (Addable) && (Addable) auto add2(auto:18, auto:19) [with auto:18 = Foo; auto:19 = Foo]': concept_auto.cc:17:20: required from here concept_auto.cc:8:9: required for the satisfaction of 'Addable' [with auto:18 = Foo] concept_auto.cc:8:19: in requirements with 'T a', 'T b' [with T = Foo] concept_auto.cc:8:42: note: the required expression '(a + b)' is invalid 8 | concept Addable = requires(T a, T b) { a + b; };
需要GCC(10.0+),Clang(10.0+),编译选项:-std=c++20/-std=c++2a
https://en.cppreference.com/w/cpp/compiler_support
自C++20提供的concept之后,我们不再需要enable_if/SFINAE的机制、函数重载来做一些模版约束检查了,使用concept可以帮你搞定这个操作,它提供了一种更清晰和强大的模板参数约束机制,使得模板代码更易于编写、理解和维护。通过在编译时进行类型检查,它有助于提高代码的稳健性和可读性。
源码获取👇:
往期回顾:
热度更新,手把手实现工业级线程池
C++ 多值返回:从版本1到版本6秒杀
上一篇:前端面试题集合五(css)