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
值。但对于WithoutEqual
和WithoutUnEqual
它们缺失了一个,因此就会报错。
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. 参考
主要翻译或者是抄得