C++ 20: Concepts 与Requires

1. 简介

C++的泛型编程使得一个函数可以对不同参数类型的进行使用,

但有时候参数类型不对的时候就有一大堆的报错,好像跟SFIANE这个

特性有关,没有仔细研究过。

我自己的理解Concepts就是对模板类型的参数进行一个限制的东西,

这样在编译期间我们就可以知道哪里可能出错了。

2. Conceps使用例子

下面的代码就是一个例子,来求最大公约数的。

我们直接使用标准库里面的std::is_integral来判断。

cpp 复制代码
#include <iostream>
#include <type_traits>
#include <iostream>
template <typename T>
concept Integral = std::is_integral<T>::value;

Integral auto gcd(Integral auto a, Integral auto b)
{
    if (b == 0) return a;
    else return gcd(b, a % b);
}

int main()
{

    auto g1 = gcd(10,5);
    // auto g2 = gcd(10.1,2.2);

    std::cout << g1 << std::endl;

    return 0;
}

如果我们把g2的注释就会直接报错说类型不匹配

而如果我们不使用concept进行限制的话

cpp 复制代码
template<typename T>
T gcd(T a, T b)
{
    return b == 0 ? a : gcd(b, a % b);
}

错误就会在实例化期间才会被捕捉到。

上面的concepts的其实是加了语法糖的,它的原始形式是下面的这样。

requires就是表示这个模板参数需要满足concept所对应的限制。

cpp 复制代码
template<typename T>
requires Integral<T>
T gcd(T a, T b)
{
    return b == 0 ? a : gcd(b, a % b);
}

定义concepts的语法格式

cpp 复制代码
template <template-parameter-list>
concept concept-name = constraint-expression;

限制的表达式大体分两种

  • 通过与&&||和非!进行连接的

限制表达式是一个在编译期就可以决定的bool值,

下面例子就是来判断,是不是有符号整数和无符号整数。

cpp 复制代码
// SignedUnsignedIntegrals.cpp

#include <iostream>

template <typename T>
concept Integral = std::is_integral<T>::value;

template <typename T>
concept SignedIntegral = Integral<T> && std::is_signed<T>::value;

template <typename T>
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;

void func(SignedIntegral auto integ) {               // (1)
    std::cout << "SignedIntegral: " << integ << '\n';
}

void func(UnsignedIntegral auto integ) {             // (2)
    std::cout << "UnsignedIntegral: " << integ << '\n';
}

int main() {

    std::cout << '\n';

    func(-5);
    func(5u);

    std::cout << '\n';

}

另外 一种就是requires表达式

3. 使用requires定义concept

我们先来看下requires的语法格式

cpp 复制代码
requires (parameter-list(optional)) {requirement-seq} 

requires总共分四种类形的限制

  • 简单限制
  • 类型限制
  • 复合限制
  • 内嵌限制
3.1 简单限制
cpp 复制代码
template<typename T>
concept Addable = requires (T a, T b) {
    a + b;
};
3.2 类型限制

类型它有没有相应的变量名,有没有办法构造另外一个对象。

cpp 复制代码
#include <iostream>
#include <vector>

template <typename>
struct Other;  

template <>
struct Other<std::vector<int>> {};

template<typename T>
concept TypeRequirement = requires {
    typename T::value_type;             // (2)
    typename Other<T>;                  // (3)
};                         

int main() {

    TypeRequirement auto myVec= std::vector<int>{1, 2, 3};  // (1)

}
3.3 复合限制

语法格式

cpp 复制代码
{expression} noexcept(optional) 
return-type-requirement(optional);    

下面就是一个例子,定义了相等这个概念,它要求对象的!===

运算符最终都可以转换为bool值。但对于WithoutEqualWithoutUnEqual它们缺失了一个,因此就会报错。

cpp 复制代码
#include <concepts>
#include <iostream>

template<typename T>                                     // (1)
concept Equal = requires(T a, T b) {
    { a == b } -> std::convertible_to<bool>;
    { a != b } -> std::convertible_to<bool>;
};

bool areEqual(Equal auto a, Equal auto b){
  return a == b;
}

struct WithoutEqual{                                       // (2)
  bool operator==(const WithoutEqual& other) = delete;
};

struct WithoutUnequal{                                     // (3)
  bool operator!=(const WithoutUnequal& other) = delete;
};

int main() {
 
    std::cout << std::boolalpha << '\n';
    std::cout << "areEqual(1, 5): " << areEqual(1, 5) << '\n';
 
    /*
 
    bool res = areEqual(WithoutEqual(),  WithoutEqual());    // (4)
    bool res2 = areEqual(WithoutUnequal(),  WithoutUnequal());
 
    */

    std::cout << '\n';
 
}

同样再举例

cpp 复制代码
template<class T>
concept TCPDatagramAdapter = requires( T a, TCPMessage seg ) {
  { a.write( seg ) } -> std::same_as<void>;

  { a.read() } -> std::same_as<std::optional<TCPMessage>>;
};

这个adapter就要求a.write( seg )的返回值为空,且读取的返回值是std::optional<TCPMessage>

3.4 内嵌限制

就是在定义concept时,加在后面,比如说下面的代码;

用了两种方式来定义UnsignedIntegral这个概念,

后面一种就是用了内嵌的require,但是这里不使用内嵌可能

可读性更好,当然这只是为了举例。

cpp 复制代码
// nestedRequirements.cpp

#include <type_traits>

template <typename T>
concept Integral = std::is_integral<T>::value;

template <typename T>
concept SignedIntegral = Integral<T> && std::is_signed<T>::value;

// template <typename T>                               // (2)
// concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;

template <typename T>                                  // (1)
concept UnsignedIntegral = Integral<T> &&
requires(T) {
    requires !SignedIntegral<T>;
};

int main() {

    UnsignedIntegral auto n = 5u;  // works
    // UnsignedIntegral auto m = 5;   // compile time error, 5 is a signed literal

}

4. 参考

主要翻译或者是抄得

modercpp-concepts-requires
modercpp-defining-concept

相关推荐
好学且牛逼的马1 小时前
GOLANG 接口
开发语言·golang
ahauedu1 小时前
AI资深 Java 研发专家系统解析Java 中常见的 Queue实现类
java·开发语言·中间件
韭菜钟1 小时前
在Qt中用cmake实现类似pri文件的功能
开发语言·qt·系统架构
闲人编程2 小时前
Python第三方库IPFS-API使用详解:构建去中心化应用的完整指南
开发语言·python·去中心化·内存·寻址·存储·ipfs
CTRA王大大3 小时前
【golang】制作linux环境+golang的Dockerfile | 如何下载golang镜像源
linux·开发语言·docker·golang
zhangfeng11333 小时前
以下是基于图论的归一化切割(Normalized Cut)图像分割工具的完整实现,结合Tkinter界面设计及Python代码示
开发语言·python·图论
还梦呦4 小时前
2025年09月计算机二级Java选择题每日一练——第五期
java·开发语言·计算机二级
鱼鱼说测试5 小时前
postman接口自动化测试
开发语言·lua
從南走到北5 小时前
JAVA国际版东郊到家同城按摩服务美容美发私教到店服务系统源码支持Android+IOS+H5
android·java·开发语言·ios·微信·微信小程序·小程序