专业的编程技术博客社区

网站首页 > 博客文章 正文

C++20尝鲜:概念(Concept)(c++ concepts)

baijin 2024-08-13 00:56:23 博客文章 9 ℃ 0 评论

介绍

类模板,函数模板,以及非模板函数(常为类模板的成员),可以与约束(constraint)关联,它指定对模板实参的一些要求,这些要求可被用于选择最恰当的函数重载模板特化

这种要求的具名集合被称为概念(concept)。每个概念都是谓词,于编译时求值,并成为以之作为一项约束的模板接口的一部分。

具名要求(被命名的条件) --> 约束 --> 概念

概念是用来约束模板类型的条件集合。原来模板通过SIFNAE机制时,报错信息难以阅读,匹配的逻辑难写难读懂,并且这些约束条件和模板本身绑定在一起,不易复用。所以提出概念就是为了解决上述问题,它通过将模板的类型约束条件抽象出来,然后在模板定义时再使用它。这样成功解耦了模板类型约束条件和模板本身。

概念的目的是塑造语义分类(Number、Range、RegularFunction)而非语法上的限制(HasPlus、Array)。按照 ISO C++ 核心方针 T.20 所说,“与语法限制相反,指定有意义语义的能力是真正的概念的决定性特征。”

概念通过约束表达式定义,使用概念的地方可以直接使用requires约束表达式。

约束、概念和 requires表达式都是编译器常量表达式 bool 值,并且可以作为普通值使用,例如在 if constexpr 中。

概念定义的形式:

template < 模板形参列表 >

concept 概念名 = 约束表达式;

template < class T >
concept integral = std::is_integral_v<T>;


约束

约束是逻辑操作和操作数的序列,它指定对于模板实参的要求。它们可在 requires 表达式中出现,也可直接作为概念的主体

有三种类型的约束:

1) 合取 &&(conjunction)

2) 析取 ||(disjunction)

3) 原子约束(atomic constraint)

原子约束应当为 bool 类型的纯右值常量表达式,当且仅当它求值为 true 时该约束得以满足。

template <class T>
concept Integral = std::is_integral<T>::value;
template <class T>
concept SignedIntegral = Integral<T> && std::is_signed<T>::value;
template <class T>
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;
template <class T = void>
    requires EqualityComparable<T> || Same<T, void>
struct equal_to;

requires表达式:

requires 表达式的语法如下:

requires { requirement-seq }

requires ( 形参列表(可选) ) { 要求序列 }

它是 bool 类型的纯右值表达式,描述对一些模板实参的约束。若约束得到满足则这种表达式为 true,否则为 false:

template<typename T>
concept Addable = requires (T x) { x + x; }; // requires 表达式,用于定义概念
 
template<typename T> requires Addable<T> // requires 子句,非 requires 表达式
T add(T a, T b) { return a + b; }
 
template<typename T>
    requires requires (T x) { x + x; } // 第一个是requires 子句,第二个是requires 表达式
T add(T a, T b) { return a + b; }

要求序列 中的每个要求是下列之一:

  • 简单要求(simple requirement)
template<typename T>
concept Addable =
requires (T a, T b) {
    a + b; // 该表达式是不求值操作数;只检查语言正确性。
};
  • 类型要求(type requirement)
template<typename T> using Ref = T&;
template<typename T> concept C =
requires {
    typename T::inner; // 要求的嵌套成员名
    typename S<T>;     // 要求的类模板特化
    typename Ref<T>;   // 要求的别名模板替换
};
  • 复合要求(compound requirement)

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

template<typename T> concept C2 =
requires(T x) {
    {*x} -> std::convertible_to<typename T::inner>; // 表达式 *x 必须合法
                                                    // 并且 类型 T::inner 必须合法
                                                    // 并且 *x 的结果必须可以转换为 T::inner
    {x + 1} -> std::same_as<int>; // 表达式 x + 1 必须合法
                                  // 并且 std::Same<decltype((x + 1)), int> 必须被满足
                                  // 亦即,(x + 1) 必须为 int 类型的纯右值
    {x * 1} -> std::convertible_to<T>; // 表达式 x * 1 必须合法
                                       // 并且其结果必须可以转换为 T
};
  • 嵌套要求(nested requirement)
template <class T>
concept Semiregular = DefaultConstructible<T> &&
    CopyConstructible<T> && Destructible<T> && CopyAssignable<T> &&
requires(T a, size_t n) {  
    requires Same<T*, decltype(&a)>;  // 嵌套:“Same<...> 求值为 true”
    { a.~T() } noexcept;  // 复合:"a.~T()" 是不抛出的合法表达式
    requires Same<T*, decltype(new T)>; // 嵌套:“Same<...> 求值为 true”
    requires Same<T*, decltype(new T[n])>; // 嵌套
    { delete new T };  // 复合
    { delete new T[n] }; // 复合
};


模板引入概念的形式(requires子句):

概念 auto

template < 模板形参列表 >

requires 概念

T func(T a)

template < 模板形参列表 >

T func(T a) requires 概念

template < 概念 T > 去除typename关键字

T func(T a)



例子1:

#include <iostream>
#include <concepts>

std::integral auto add1(std::integral auto a, std::integral auto b)
{
    return a + b;
}

template<typename T> 
requires std::integral<T>
T add2(T a, T b)
{
    return a + b;
}

template<typename T>
T add3(T a, T b) requires std::integral<T>
{
    return a + b;
}

template<std::integral T>
T add4(T a, T b)
{
    return a + b;
}

int main(int argc, char *argv[])
{
    std::cout << add1(1, 2) << std::endl;
    std::cout << add2(1, 2) << std::endl;
    std::cout << add3(1, 2) << std::endl;
    std::cout << add4(1, 2) << std::endl;
    return 0;
}

例子2:

#include <iostream>
#include <concepts>
#include <stddef.h>

template<class T>
concept A = requires (T t) {
    t.has();
} && !requires (T t) {
    t.hasnt();
};

template<class T>
concept B = requires (T t) {
    requires requires { t.has(); };
    requires !requires { t.hasnt(); };
};

template<class T>
concept C = requires (T t) {
    t.has();
    !requires { t.hasnt(); };
};

struct S1 {};
struct S2 { void has(); };
struct S3 { void hasnt(); };
struct S4 { void has(); void hasnt(); };

int main(int argc, char *argv[])
{
    static_assert(!A<S1>);
    static_assert(A<S2>);
    static_assert(!A<S3>);
    static_assert(!A<S4>);

    static_assert(!B<S1>);
    static_assert(B<S2>);
    static_assert(!B<S3>);
    static_assert(!B<S4>);

    static_assert(!C<S1>);
    static_assert(C<S2>);
    static_assert(!C<S3>);
    static_assert(C<S4>);
    
    return 0;
}
https://wandbox.org/nojs/gcc-head
https://wandbox.org/nojs/clang-head

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表