目录
一.C++20

1.概念
概念是C++20引入的模板参数约束机制,它允许程序员明确指定模板参数必须满足的条件。概念本质山是一个编译时谓词,用于验证模板参数是否满足特定要求。概念会在模板实例化前检查类型是否满足条件,而不是在实例化后产生难以理解的错误。
a.定义
在C++20中,使用concept关键字来定义概念。基本语法如下:
cpp
template <template-parameter-list>
concept concept-name = constraint-expression;
template-parameter-list是模板参数列表,concept-name是概念的名称,constraint-expression是一个可以被求值为bool的常量表达式,通常是各种约束的组合。
常见的几种概念定义:
cpp
template<typename T>
concept Integral = std::is_integral_v<T>;
concept Integral=std::is_integral_v<T>,使用标准库类型萃取,定义一个名为Integral的概念,要求传入的模板参数T必须是一个整数类型。
cpp
template<typename T>
concept SmallType = sizeof(T) <= 4;
concept SmallType=sizeof(T)<=4,使用一个简单的常量表达式,定义一个名为SmallType的概念,sizeof(T)<=4为约束条件,sizeof(T)会在编译期计算出类型T的字节大小。这个表达式要求T的大小必须小于或等于4个字节。
cpp
template<typename T>
concept Incrementable = requires(T t) {
++t;
t++;
};
concept Incrementable=requires(T t){ ++t;t++;};,使用一个简单的requires子句,构造了一个要求表达式,含义为给定一个类型为T的对象t,++t要求类型T必须支持前置自增操作,t++要求类型T必须支持后置自增操作。编译期的逻辑为如果编译器传入类型T后,发现++t或者t++,这两条语句中的任何一个产生编译错误,那么Incrementable<T>的结果就是false,如果两个语句都能成功编译,结果就是true。
b.requires
requires表达式有以下几种应用形式:
cpp
template<typename T>
concept Addable = requires(T a, T b) {
a + b;
};
concept Addable=requires(T a,T b){ a+b;},定义一个名为Addable的概念,requires表达式用于检测类型T是否支持加法操作。
cpp
template<typename T>
concept HasValueType = requires {
typename T::iterator;
typename T::value_type;
};
concept HasValueType=requires{ typename T::iterator; typename T::value_type;},以上代码展示了requires表达式的另一个重要用法:类型要求,即检查类型内部是否定义了特定的成员类型。编译期逻辑为编译器传入类型T后,会尝试去T的作用域里找iterator和value_type类型,如果找不到,就会发生替换失败,该概念判定为false。如果都找到了,则为true。
cpp
template<typename T>
concept ConvertibleAddable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
{ a += b } noexcept -> std::same_as<T&>;
};
concept ConvertibleAddable=requires(T a,T b){ { a+b }->std::convertible_to<T>,{ a+=b } noexcept -> std::same_as<T&>;},以上代码展示了requires表达式最高级、也最实用的功能:复合要求。std::convertible_to<T>为返回值约束,要求a+b计算出来的结果必须能够隐式转换为类型T。{a+=b}要求a+=b必须合法,noexcept为异常约束,要求a+=b这个操作在执行时绝对不能抛出异常。std::same_as<T&>,为精确返回值约束,要求a+=b的返回值必须严格等于T&,std::same_as不允许隐式转换。
cpp
template<typename T>
concept ComplexConcept = requires(T t) {
requires sizeof(T) <= sizeof(long);
requires std::is_class_v<T>;
};
concept ComplexConcept = requires(T t){ requires sizeof(T)<=sizeof(long); requires std::is_class_v<T>;},以上代码展示了requires表达式的最后一个高级特性:嵌套要求。requires sizeof(T)<=sizeof(long),要求sizeof(T)<=sizeof(long)该常量表达式的结果为true,相当于一个编译期断言。requires std::is_class_v<T>,同理要求std::is_class_v<T>为true,即T必须是一个class或struct类型。总的来说,这个概念要求类型T必须是一个类,并且其大小不能超过long的大小。
c.使用
cpp
#include<iostream>
#include<string>
#include<vector>
#include<concepts>
template<class T>
concept Integral = std::is_integral_v<T>;
template<class T>
concept Float = std::is_floating_point_v<T>;
template<Integral T>
void f1(T x)
{
std::cout << "有concepts 约束" << std::endl;
}
template<typename T>
requires Integral<T> || Float<T>
void f2(T x) {}
template<typename T>
void f3(T x) requires Integral<T> || Float<T> {}
template<typename T>
requires requires(T x) { x.size(); x+x; }
void f4(T x) {}
void f5(Integral auto x) {}
concept Integral=std::is_integral_v<T>,concept Float=std::is_floating_point_v<T>,通过标准库的类型萃取工具定义了整数类型的Integral概念和浮点类型的Float概念,随后利用这些概念作为模板参数的限制条件编写了函数f1、f2、f3、f4、f5。当在主函数中调用该函数时,编译器会根据传入的实参类型自动匹配并分发到满足对应概念约束的函数版本中,若传入的值不满足任何一个已定义的概念,编译器将在编译阶段阻止调用。
2.约束
原子约束,是不可再分的约束表达式,如一个概念名std::integral<T>,一个requires表达式,一个常量布尔表达式等。
合取,逻辑与,所有约束都必须为true,整个合取约束才为true,使用&&运算符连接多个原子约束。
析取,逻辑或,多个约束中至少有一个为true,整个析取约束就为true,使用||运算符连接多个约束。
折叠扩展约束,可变参数包的约束扩展,对参数包的多个参数进行包扩展的约束,C++26才开始支持。
cpp
// 1. 逻辑与(&&)
template<typename T>
concept IntegralAndPrintable = std::integral<T> && requires(T t) {
std::cout << t;
};
// 2. 逻辑或(||)
template<typename T>
concept Number = std::integral<T> || std::floating_point<T>;
// 3. 逻辑⾮(!)
template<typename T>
concept NonPointer = !std::is_pointer_v<T>;
// 4. 折叠扩展约束,折叠扩展约束就是可变参数包的扩展,C++26才开始⽀持
template <class T> concept A = std::is_move_constructible_v<T>;
template <class T> concept B = std::is_copy_constructible_v<T>;
template <class T> concept C = A<T> && B<T>;
template <class... T>
requires (C<T> && ...) void g(T...) {
cout << "g()->C" << endl;
}
以上代码完整演示了在C++20中如何对概念进行逻辑运算与组合,以及C++26引入is的包扩展约束。concept IntegralAndPrintable=std::integral<T>&&requires(T t){std::cout<<t;},将标准库的整数概念与自定义的requires表达式结合,形成更严格的约束;concept Number=std::integral<T>||std::floating_point<T>,使用逻辑或,将整数和浮点数概念合并,共同构成广义的数字概念;concept NonPointer=!std::is_pointer_v<T>,使用逻辑非,通过取反指针类型萃取来排除指针。template<class T> concept A=std::is_move_constructible_v<T>,template<class T> concept B=std::is_copy_constructible_v<T>,template<class T> concept C=A<T> && B<T>,这段代码则演示了可变参数模板中的折叠表达式约束,先组合出要求既可移动又可拷贝的独立概念C,再在函数g的requires子句中利用(C<T>&& ...)对所有参数包展开并做逻辑与运算,从而要求传入的所有参数都必须同时满足可移动和可拷贝的构造条件。
a.偏序规则
C++20概念的约束偏序规则用于在函数重载或模板特化时,判断哪个约束的模板更特化或更受约束。编译器使用这个规则来选择最合适的模板。
偏序规则的核心是:如果约束A包含约束B,那么A比B更受约束。这意味着任何满足A的类型也一定会满足B,但满足B的类型不一定满足A。当两个模板匹配时,编译器会选择更受约束的那一个。
cpp
#include<iostream>
#include<concepts>
template<class T>
concept Integral = std::is_integral_v<T>;
template<Integral T>
void f(T x)
{
std::cout << "有 concepts 约束" << std::endl;
}
template<class T>
void f(T x)
{
std::cout << "⽆ concepts 约束" << std::endl;
}
template<class T> concept A = std::is_move_constructible_v<T>;
template<class T> concept B = std::is_copy_constructible_v<T>;
template<class T> concept C = A<T> && B<T>;
template<class T>
requires A<T> void g(T x)
{
std::cout << "g()->A" << std::endl;
}
template <class T>
requires C<T> void g(T x)
{
std::cout << "g()->C" << std::endl;
}
以上代码展示了C++20概念两个核心机制:重载决议的优先级与约束的偏序规则。concept integral=std::is_integral_v<T>,template<integral T>,template<class T>,首先在函数f的重载中,代码定义了一个有概念约束的版本(要求整数类型)和一个无约束的模板版本。当在代码中调用f(10)时,虽然两个模板都能匹配,但编译器会优先选择约束更强、更特化的带Concepts版本。template<class T> concept A=std::is_move_constructible_v<T>,template<class T> concept B=std::is_copy_constructible_v<T>,template<class T> concept C=A<T>&&B<T>,在函数g的重载中,体现了约束的偏序规则。函数g有两个版本:版本一要求类型满足概念A(可移动构造),版本二要求满足概念C(C由A和B通过逻辑与组合而成,即同时满足可移动和可拷贝)。当调用g并传入一个既满足A又满足C的类型时,两个版本的约束条件都成立。但编译器在比对约束时,发现C的逻辑条件包含了A。根据偏序规则,编译器会选择版本二。
b.SFINAE与Concepts
SFINAE是Substitution Failure is Not An Error的缩写,翻译为替换失败不是错误,通过模板替换失败静默排除候选函数,实现编译期条件分支。本质是技巧性机制,依赖模板实例化规则如std::enable_if、decltype等。
Concepts直接声明模板参数的语义约束,提供显式接口描述。目标是提升代码可读性和编译错误信息。SFINAE代码冗长且晦涩,Concepts是SFINAE的官方替代方案,解决了后者在可维护性和表达力上的缺陷。
cpp
// C++17:使⽤enable_if约束整型参数
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
void foo(T x) {}
// C++20:使⽤concept约束
template<std::integral T>
void foo(T x) {}
二、结语
C++20的概念与约束不仅是对传统SFINAE机制的现代化封装,更是泛型编程范式的重大革新。它将过去隐式、晦涩且难以调试的模板推导,转化为显式且语义清晰的编译期契约,极大提升了代码的可读性与报错信息的精准度。掌握这一特性,是构建现代C++泛型工程体系的基石。