首页 > 编程语言 >[C++] C++20约束表达式和requires子句

[C++] C++20约束表达式和requires子句

时间:2024-07-10 23:29:43浏览次数:18  
标签:std 20 int C++ 约束 template 子句 requires 表达式

约束


约束是逻辑操作和操作数的序列,它指定了对模板实参的要求。

  1. 合取

    两个约束的合取是用 && 运算符。

    template <typename T>
    concept luser = std::integral<T> && std::signed_integral<T>;
    

需要约束同时满足两个要求。合取判断的时候,使用短路检测,即对 std::integral<T> 结果为false时,不再对 std::signed_integral<T> 进行判断。

  1. 析取

    两个约束的析取是用 || 运算符。

    template <typename T>
    concept luser = std::integral<T> || std::signed_integral<T>;
    

需要约束满足任意一个要求。析取判断的时候,使用短路检测,即对 std::integral<T> 结果为ture时,不再对 std::signed_integral<T> 进行判断。

  1. 原子约束

    原子约束,不会是逻辑与(AND)或者逻辑或(OR)表达式(它们分别构成析取和合取)。
    在替换后的类型必须严格为 bool。不能有任何转换:

template <typename T>
requires(S<T>{})
void f(T){};  // #1

void f(int) {
    cout << "1" << endl;
};  // #2

void g() {
    f(10);  // 错误:检查 #1 时 S<int>{} 不具有 bool 类型,
            // 尽管 #2 能更好地匹配
}

如果两个原子约束由在 源码层面上相同 的表达式组成,且它们的形参映射等价,那么认为它们等同。

template <typename T, typename U>
concept luser1 = std::is_same_v<T, U>;

template <typename T, typename U>
concept luser2 = std::is_same_v<U, T>;

luser1luser2 是两个不同的概念,因为在源码层面上不同,一个是 <T, U> ,一个是<U, T>。

template <typename T, luser1<T> U>
void fun() {
}

template <typename T, luser2<T> U>
void fun() {
}

int main(int argc, char* argv[]) {
    fun(1, 2); // 会有编译错误 找不到匹配的函数因为2个约束均可满足

    return 0;
}

运行结果

gcc13.2.0编译报错:
no matching function for call to ‘funtest(int, int)’

测试结果

requires表达式

要求产生描述约束的bool类型的纯右值表达式。

语法:其中,参数列表可以没有。

requires( 参数列表 ) { 要求序列 }
简单要求

不以 requires 关键字开头的不求值表达式,仅仅对表达式做正确性检测。

把类型代入约束中,符合语法返回true,不符合语法返回false。

template <typename T>
concept loser = requires(T a, T b) {
    a + b;
};

该约束 loser ,需要保证 a + b 为正确表达式即可,基本类型或者重载 operator+

struct Test {
    void operator+(Test&){};
};

int main(int argc, char* argv[]) {
    std::cout << std::boolalpha << loser<int> << "\n";  // true
    std::cout << std::boolalpha << loser<Test> << "\n"; // true
    std::cout << std::boolalpha << loser<string> << "\n";


    return 0;
}
类型要求

要求关键字 typename 后面接一个可选的类型名称。要求指定类型名称是有效的:可以用来验证某个命名的嵌套类型是否存在,或者某个类模板特化是否命名了某个类型,或者某个别名模板特化是否命名了某个类型。命名类模板特化的类型要求并不要求该类型是完整的。

template <typename T>
using Ref = T&;

template <typename T>
concept luser = requires {
    typename T::value;
    typename Ref<T>;
};
复合要求

{ 表达式 } noexcept(可选) 返回类型要求(可选);

替换和语义约束检测按照顺序进行:

  1. 模板参数(若存在)被替换
  2. 如果使用noexcept,一定不能潜在抛出异常
  3. 如果返回类型要求存在,则:
    1. 模板参数被替换到返回类型要求。
    2. decltype((expression)) 必须满足 类型约束 的约束。否则,被包含的 requires 表达式false
template<typename T>
concept C2 = requires(T x)
{
    // 表达式 *x 必须合法
    // 并且 类型 T::inner 必须存在
    // 并且 *x 的结果必须可以转换为 T::inner
    {*x} -> std::convertible_to<typename T::inner>;
 
    // 表达式 x + 1 必须合法
    // 并且 std::same_as<decltype((x + 1)), int> 必须满足
    // 即, (x + 1) 必须为 int 类型的纯右值
    {x + 1} -> std::same_as<int>;
 
    // 表达式 x * 1 必须合法
    // 并且 它的结果必须可以转换为 T
    {x * 1} -> std::convertible_to<T>;
};
嵌套要求

嵌套要求具有如下形式

requires 约束表达式;

template <typename T>
concept luser3 = requires(T a) {
    requires luser<T>;
};

在约束表达式中,嵌套约束表达式。

requires子句

使用 requires 引用 requires子句 ,可以对模板参数、函数声明进行约束。

template <typename T>
void fun(T a)
    requires(std::output_iterator<T, std::iter_value_t<T>>)
{
}

这种情况下,关键词 requires 必须后随某个常量表达式(因此可以写成 requires true ),但这是为了使用一个具名概念(如上例),具名概念的一条合取/析取,或者一个 requires 表达式

表达式必须具有下列形式之一:

  • 初等表达式: requires表达式 或任何带括号的表达式
  • 以运算符 && 联结的初等表达式的序列
  • 以运算符 || 联结的前述表达式的序列
约束偏序

首先转换 P 为析取范式并转换 Q 为合取范式。当且仅当以下情况下 P 归入 Q

  • P 的析取范式中的每个析取子句都能归入 Q 的合取范式中的每个合取子句,其中
    • 当且仅当析取子句中存在不可分割约束 U 而合取子句中存在原子约束 V,使得 U 归入 V 时,析取子句能归入合取子句;
    • 当且仅当使用所述的规则判定为等同时,称不可分割约束 A 能归入原子约

简单来说就是P比Q更受约束,或者说约束条件更多。

template<typename T>
concept Decrementable = requires(T t) { --t; };
template<typename T>
concept RevIterator = Decrementable<T> && requires(T t) { *t; };
 
// RevIterator 能归入 Decrementable,RevIterator比Decrementable更受约束,但反之不行
 
template<Decrementable T>
void f(T); // #1
 
template<RevIterator T>
void f(T); // #2,比 #1 更受约束

  

f(0);       // int 只满足 Decrementable,选择 #1
f((int*)0); // int* 满足两个约束,选择 #2,因为它更受约束

标签:std,20,int,C++,约束,template,子句,requires,表达式
From: https://blog.csdn.net/qq_40939614/article/details/140336785

相关文章

  • 【国赛赛题详解】2024年数学建模国赛ABCDEF题(点个关注,后续会更新)
     您的点赞收藏是我继续更新的最大动力!一定要点击如下的蓝色字体链接,那是获取资料的入口!点击链接加入群聊【2024国赛资料合集】:http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=eQt5WRIvc5-fogZRrrahAhbqDa2nKfW8&authKey=%2BqQfThTxNnhw5LGJFRIcneF8JXBj1ufd2K01UpKPrpcgkKDskF......
  • 【国赛赛题详解】2024年数学建模国赛ABCDEF题(点个关注,后续会更新)
    您的点赞收藏是我继续更新的最大动力!一定要点击如下的蓝色字体链接,那是获取资料的入口!点击链接加入群聊【2024国赛资料合集】:http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=eQt5WRIvc5-fogZRrrahAhbqDa2nKfW8&authKey=%2BqQfThTxNnhw5LGJFRIcneF8JXBj1ufd2K01UpKPrpcgkKDskFkr......
  • 2024最新Zibll子比主题V7.7版本源码 开心版 | WordPress主题
    简介:2024最新Zibll子比主题V7.7版本源码开心版|WordPress主题安装教程在压缩包内V7.7更新日志:新功能新增数字翻页输入页码跳转的功能(注:总页数超过8页才会显示)新增后台批量设置文章阅读量、点赞数、显示布局等文章扩展功能新增后台批量设置论坛帖子阅读量、置顶、类......
  • 「字符串」Manacher算法(马拉车)/ LeetCode 05(C++)
    给你一个字符串 s,找到 s 中最长的回文子串。示例1:输入:s="babad"输出:"bab"解释:"aba"同样是符合题意的答案。示例2:输入:s="cbbd"输出:"bb"思路我们回想中心扩散法:某字符处的中心扩散完毕后,其实已经将它身前身后的字符段落都搜索过了,那么如果我们搜索其后的字......
  • 提速下载,不再等待!2024Internet Download Manager超实用评测
    ......
  • CorelDRAW2024新版本来咯!你的设计神助手
    ......
  • YC312A [ 20240702 CQYC省选模拟赛 T1 ] 第一题(diyiti)
    题意给定一个长度为\(n\)的可重集,以及正整数\(k\)。设一个子集的价值为子集中最大值减去最小值,你需要将这个可重集划分为\(k\)个子集,使得价值之和最小,子集需要满足不重。\(n,k\le100\)。Sol思考一下发现如果不记录每个子集的信息是不好做的。考虑将所有子集的大小记......
  • NOIP2024模拟2
    NOIP2024模拟2都不会,哈哈哈我在此发表暴论,在\(T4\)放签到题的都是SB。做不出来的更SB。T1:酸碱度中和签到题。排序,二分答案,记录一下这一组的最小的,最小的和最多的差大于二倍答案就新开一组。T2:聪明的小明状压。50pts是显然状压,考虑延续其思路。压出状态发现只有......
  • P1039[NOIP2003提高组]侦探推理
    暂时未完成qwq[NOIP2003提高组]侦探推理(这道题思路很简单,但是细节一大堆qwq,调吐了QAQ这个题一共就20个人,星期一共就有7种可能,100句证词,所以可以直接暴力枚举,看一看假设第$i$个人是罪犯(guilty),今天是星期$j$,那么一共有几个人说了谎话。然后就好了awa…………了吗……这......
  • c++ protobuf安装记录
    googleprotobuf是一个灵活的、高效的用于序列化数据的协议。相比较XML和JSON格式,protobuf更小、更快、更便捷。googleprotobuf是跨语言的,并且自带了一个编译器(protoc),只需要用它进行编译,可以编译成Java、python、C++、C#、Go等代码,然后就可以直接使用,不需要再写其他代码,自带有......