什么是C ++概念?
概念(concepts)就是一种编译时谓词,指出一个或多个类型应如何使用1
概念(concepts)提供基础语言概念的定义,它们能用于进行模板实参的编译时校验,以及基于类型属性的函数派发。这些概念在程序中提供等式推理的基础。2
通过使用concepts,传统的模板元编程方面关于编译错误的痛点可以得到极大改善,编译器可以给出更加符合人类直觉的错误提示。3
概念是用于表达通用算法对其模板参数的期望的谓词。
概念允许您正式记录模板上的约束,并让编译器强制执行。另外,您还可以利用这种强制执行功能,通过基于概念的重载来缩短程序的编译时间。4
concepts标准库中的方法与type_trais很像。解决的问题与SFINAE5接近。
标准库中的大多数概念一同加上了语法及语义要求。通常,编译器只能检查语法要求。若在使用点语义要求未得到满足,则程序为病式,不要求诊断。
在vs2019的16.4.1中,已经是标准头文件了。
C++20 concepts标准库中提供的基本方法 6 7 8
类型 | 方法 | 功能 |
---|---|---|
核心语言概念 | same_as | 指定一个类型与另一类型相同 |
derived_from | 指定一个类型派生自另一类型 | |
convertible_to | 指定一个类型能隐式转换成另一类型 | |
common_reference_with | 指定两个类型共有一个公共引用类型 | |
common_with | 指定两个类型共有一个公共类型 | |
integral | 指定类型为整型类型 | |
signed_integral | 指定类型为有符号的整型类型 | |
unsigned_integral | 指定类型为无符号的整型类型 | |
floating_point | 指定类型为浮点类型 | |
assignable_from | 指定一个类型能从另一类型赋值 | |
swappable swappable_with |
指定一个类型能进行交换,或两个类型能彼此交换 | |
destructible | 指定能销毁该类型的对象 | |
constructible_from | 指定该类型的变量能从一组实参类型进行构造,或绑定到一组实参类型 | |
default_constructible | 指定能默认构造一个类型的对象 | |
move_constructible | 指定能移动构造一个类型的对象 | |
copy_constructible | 指定能复制构造和移动构造一个类型的对象 | |
比较概念 | boolean | 指定类型能用于布尔语境 |
equality_comparable equality_comparable_with |
指定运算符 == 为等价关系 | |
totally_ordered totally_ordered_with |
指定比较运算符在该类型上产生全序 | |
对象概念 | movable | 指定能移动及交换一个类型的对象 |
copyable equality_comparable_with |
指定能复制、移动及交换一个类型的对象 | |
semiregular totally_ordered_with |
指定能赋值、移动、交换及默认构造一个类型的对象 | |
regular totally_ordered_with |
指定类型为正则,即它既为 semiregular 亦为 equality_comparable | |
可调用概念 | invocable regular_invocable |
指定能以给定的一组实参类型调用的可调用类型 |
predicate | 指定可调用类型为布尔谓词 | |
relation | 指定可调用类型为二元关系 | |
strict_weak_order | 指定一个 relation 所强加的是严格弱序 | |
迭代器概念 | readable | 指定类型通过应用运算符 * 可读 |
writable | 指定可向迭代器所引用的对象写入值 | |
weakly_incrementable | 指定 semiregular 类型能以前后自增运算符自增 | |
incrementable | 指定 weakly_incrementable 类型上的自增操作保持相等性,而且该类型为 equality_comparable | |
input_or_output_iterator | 指定该类型对象可以自增且可以解引用 | |
sentinel_for | 指定类型为某个 input_or_output_iterator 类型的哨位类型 | |
sized_sentinel_for | 指定可对一个迭代器和一个哨位应用 - 运算符,以在常数时间计算其距离 | |
input_iterator | 指定类型为输入迭代器,即可读取其所引用的值,且可前/后自增 | |
output_iterator | 指定类型为给定的值类型的输出迭代器,即可向其写入该类型的值,且可前/后自增(概念) | |
forward_iterator | 指定 input_iterator 为向前迭代器,支持相等比较与多趟操作 | |
bidirectional_iterator | 指定 forward_iterator 为双向迭代器,支持向后移动 | |
random_access_iterator | 指定 bidirectional_iterator 为随机访问迭代器,支持常数时间内的前进和下标访问 | |
contiguous_iteartor | 指定 random_access_iterator 为连续迭代器,指代内存中连续相接的元素 | |
范围概念 | range | 指定类型为范围,即它同时提供 begin 迭代器和 end 哨位 |
safe_range | 指定类型为 range 而且能安全返回从该类型表达式获得的迭代器而无悬垂之虞 | |
sized_range | 指定范围可在常数时间内知晓其大小 | |
view | 指定范围为视图,即它拥有常数时间的复制/移动/赋值 | |
input_range | 指定范围的迭代器类型满足 input_iterator | |
output_range | 指定范围的迭代器类型满足 output_iterator | |
forward_range | 指定范围的迭代器类型满足 forward_iterator | |
bidirectional_range | 指定范围的迭代器类型满足 bidirectional_iterator | |
random_access_range | 指定范围的迭代器类型满足 random_access_iterator | |
contiguous_range | 指定范围的迭代器类型满足 contiguous_iterator | |
common_range | 指定范围拥有相同的迭代器和哨位类型 | |
viewable_range | 指定针对 range 的要求,令其可安全转换为 view |
另外,在《C++语言导学》第二版中的12章 算法 中,“12.7 概念(C++20)” 中也对上述表格中的内容做了简单介绍(P137-140)。
参考:制约与概念 (C++20 起)
下面是从Visual Studio 2019 16.3版中的C ++ 20概念和csdn blog c++20 concept上面收集的Concepts相关示例。代码位置:
https://github.com/5455945/cpp_demo/blob/master/C%2B%2B20/concepts/concepts.cpp
#include
#include
#include
#include
using namespace std;
// 1 下面demo来自微软官网:
// https://devblogs.microsoft.com/cppblog/c20-concepts-are-here-in-visual-studio-2019
//-version-16-3/?utm_source=vs_developer_news&utm_medium=referral
// This concept tests whether 'T::type' is a valid type
template
concept has_type_member = requires { typename T::type; };
struct S1 {};
struct S2 { using type = int; };
static_assert(!has_type_member);
static_assert(has_type_member);
// Currently, MSVC doesn't support requires-expressions everywhere; they only work in concept definitions and in requires-clauses
//template constexpr bool has_type_member_f(T) { return requires{ typename T::type; }; }
template constexpr bool has_type_member_f(T) { return has_type_member; }
static_assert(!has_type_member_f(S1{}));
static_assert(has_type_member_f(S2{}));
// This concept tests whether 'T::value' is a valid expression which can be implicitly converted to bool
// 'std::convertible_to' is a concept defined in
template
concept has_bool_value_member = requires { { T::value }->std::convertible_to; };
struct S3 {};
struct S4 { static constexpr bool value = true; };
struct S5 { static constexpr S3 value{}; };
static_assert(!has_bool_value_member);
static_assert(has_bool_value_member);
static_assert(!has_bool_value_member);
// The function is only a viable candidate if 'T::value' is a valid expression which can be implicitly converted to bool
template
bool get_value()
{
return T::value;
}
// This concept tests whether 't + u' is a valid expression
template
concept can_add = requires(T t, U u) { t + u; };
// The function is only a viable candidate if 't + u' is a valid expression
template requires can_add
auto add(T t, U u)
{
return t + u;
}
// 2 下面demo来自https://blog.csdn.net/oLuoJinFanHua12/article/details/101319056
//std::enable_if实现
template
void print_int0(std::enable_if_t>, T> v)
{
std::cout << v << std::endl;
}
void print_int0()
{
//print_int0(1); // error C2660: “print_int0”: 函数不接受 1 个参数
print_int0(1);
//print_int0(1.0); // error C2672: “print_int0”: 未找到匹配的重载函数
}
// 2.1.限制只能打印int类型
template
concept IntLimit = std::is_same_v>; //制约T塌陷后的类型必须与int相同
template
void print_int(T v)
{
std::cout << v << std::endl;
}
void concept_test01()
{
print_int(1);
//print_int(1.0); //error C2672: “print_int”: 未找到匹配的重载函数/error C7602: “print_int”: 未满足关联约束
}
// 2.2 2.require关键字
// 限定只能调用存在name成员函数的类
class A
{
public:
std::string_view name() const { return "A"; }
};
class B
{
public:
std::string_view class_name() const { return "B"; }
};
template
concept NameLimit = requires(T a)
{
a.name(); // 制约T的实例a必须要有name成员函数
};
template
void print_name(T a)
{
std::cout << a.name() << std::endl;
}
void concept_requires()
{
A a;
print_name(a);
//B b;
//print_name(b); // error C2672 : “print_name”: 未找到匹配的重载函数/error C7602: “print_name”: 未满足关联约束
}
// 限定只能调用返回值可以转换为std::string的函数
template
concept ReturnLimit = requires(T t)
{
{t()}->std::convertible_to; // 函数返回值必须可以转换为std::string
std::is_function; // T必须为函数
};
template
void print_string(T func)
{
std::cout << func() << std::endl;
}
std::string str1()
{
return "123";
}
constexpr const char* str2()
{
return "str2";
}
std::basic_string str3()
{
return u8"str3";
}
void concept_requires_string()
{
std::string t;
t = std::string_view("213");
print_string(&str1);
print_string(&str2);
//print_string(&str3); // error C2672: “print_string”: 未找到匹配的重载函数
}
// 2.3 concept可以和if constexpr结合使用
class A3
{
public:
constexpr std::string_view name() const { return "A3"; }
};
class B3
{
public:
constexpr std::string_view class_name() const { return "B3"; }
};
template
concept CA = requires(T a)
{
a.name();
};
template
concept CB = requires(T b)
{
b.class_name();
};
template
void print_class(const T& t)
{
if constexpr (CA)
{
std::cout << t.name() << std::endl;
}
else if constexpr (CB)
std::cout << t.class_name() << std::endl;
}
void concepts_constexpr()
{
A3 a;
B3 b;
print_class(a);
print_class(b);
}
int main() {
std::cout << add(5, 7.0) << std::endl;
print_int0();
concept_test01();
concept_requires();
concept_requires_string();
concepts_constexpr();
return 0;
}
《C++语言导学》 Bjarne Stroustrup 著,王刚 译 原书第二版, P85 ↩︎
来自https://zh.cppreference.com/w/cpp/concepts ↩︎
来自C++20 - 下一个大版本功能确定 ↩︎
来自Visual Studio 2019 16.3版中的C ++ 20概念 ↩︎
SFINAE:匹配失败并不是一个错误(Substitution failure is not an error) ↩︎
概念库 ↩︎
迭代器概念 ↩︎
范围概念 ↩︎