C++20 Concepts

01 concepts及concepts库函数

什么是C ++概念?
概念(concepts)就是一种编译时谓词,指出一个或多个类型应如何使用1

概念(concepts)提供基础语言概念的定义,它们能用于进行模板实参的编译时校验,以及基于类型属性的函数派发。这些概念是在程序中提供等式推理的基础。2

通过使用concepts,传统的模板元编程方面关于编译错误的痛点可以得到极大改善,编译器可以给出更加符合人类直觉的错误提示。3

概念是用于表达通用算法对其模板参数的期望的谓词。
概念可以正式记录模板上的约束,并让编译器强制执行。另外,还可以利用这种强制执行功能,通过基于概念的重载来缩短程序的编译时间。4

concepts标准库中的方法与type_trais很像。解决的问题与SFINAE5接近。

标准库中的大多数概念一同加上了语法及语义要求。通常,编译器只能检查语法要求。若在使用点的语义要求未得到满足,则程序为病式,不要求诊断。

<concepts>在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)。

02 Concepts语法规则

参考:制约与概念 (C++20 起)

03 Concepts demo

下面是从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 <iostream>
#include <string>
#include <cstddef>
#include <concepts>
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<typename T>
concept has_type_member = requires { typename T::type; };

struct S1 {};
struct S2 { using type = int; };

static_assert(!has_type_member<S1>);
static_assert(has_type_member<S2>);

// Currently, MSVC doesn't support requires-expressions everywhere; they only work in concept definitions and in requires-clauses
//template <class T> constexpr bool has_type_member_f(T) { return requires{ typename T::type; }; }
template <class T> constexpr bool has_type_member_f(T) { return has_type_member<T>; }

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 <concepts>
template<typename T>
concept has_bool_value_member = requires { { T::value }->std::convertible_to<bool>; };

struct S3 {};
struct S4 { static constexpr bool value = true; };
struct S5 { static constexpr S3 value{}; };

static_assert(!has_bool_value_member<S3>);
static_assert(has_bool_value_member<S4>);
static_assert(!has_bool_value_member<S5>);

// The function is only a viable candidate if 'T::value' is a valid expression which can be implicitly converted to bool
template<has_bool_value_member T>
bool get_value()
{
    return T::value;
}

// This concept tests whether 't + u' is a valid expression
template<typename T, typename U>
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<typename T, typename U> requires can_add<T, U>
auto add(T t, U u)
{
    return t + u;
}

// 2 下面demo来自https://blog.csdn.net/oLuoJinFanHua12/article/details/101319056

//std::enable_if实现
template <typename T>
void print_int0(std::enable_if_t<std::is_same_v<int, std::decay_t<T>>, T> v)
{
    std::cout << v << std::endl;
}
void print_int0()
{
    //print_int0(1);			// error C2660: “print_int0”: 函数不接受 1 个参数
    print_int0<int>(1);
    //print_int0<double>(1.0);    // error C2672: “print_int0”: 未找到匹配的重载函数
}

// 2.1.限制只能打印int类型
template <class T>
concept IntLimit = std::is_same_v<int, std::decay_t<T>>; //制约T塌陷后的类型必须与int相同

template <IntLimit T>
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 <typename T>
concept NameLimit = requires(T a)
{
    a.name();	// 制约T的实例a必须要有name成员函数
};

template <NameLimit T>
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 <typename T>
concept ReturnLimit = requires(T t)
{
    {t()}->std::convertible_to<std::string>;	// 函数返回值必须可以转换为std::string
    std::is_function<T>;						// T必须为函数
};

template <ReturnLimit T>
void print_string(T func)
{
    std::cout << func() << std::endl;
}

std::string str1()
{
    return "123";
}

constexpr const char* str2()
{
    return "str2";
}

std::basic_string<char8_t> 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 <typename T>
concept CA = requires(T a)
{
    a.name();
};

template <typename T>
concept CB = requires(T b)
{
    b.class_name();
};

template <typename T>
void print_class(const T& t)
{
    if constexpr (CA<T>)
    {
        std::cout << t.name() << std::endl;
    }
    else if constexpr (CB<T>)
        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;
}

  1. 《C++语言导学》 Bjarne Stroustrup 著,王刚 译 原书第二版, P85 ↩︎

  2. 来自https://zh.cppreference.com/w/cpp/concepts ↩︎

  3. 来自C++20 - 下一个大版本功能确定 ↩︎

  4. 来自Visual Studio 2019 16.3版中的C ++ 20概念 ↩︎

  5. SFINAE:匹配失败并不是一个错误(Substitution failure is not an error) ↩︎

  6. 概念库 ↩︎

  7. 迭代器概念 ↩︎

  8. 范围概念 ↩︎

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值