分享免费的编程资源和教程

网站首页 > 技术教程 正文

C ++ 20的Concepts快速介绍

goqiw 2024-09-14 06:53:31 技术教程 22 ℃ 0 评论

更多互联网新鲜资讯、工作奇淫技巧关注原创【飞鱼在浪屿】(日更新)

Concepts 是编写模板的革命性方法!它们可以对模板参数施加约束,以提高代码的可读性,加快编译时间并提供更好的错误消息。


什么是Concept ?

简而言之,一个Concept是在编译时对模板参数进行评估的一组约束。可以将它们用于类模板和函数模板,以控制函数重载和部分专业化。

C ++ 20为我们提供了语言支持(新关键字- requiresconcept)以及标准库中的一组预定义Concepts

换句话说,可以使用简单的语法来限制模板参数。在C ++ 20之前,有多种添加此类约束的方法。

这是一个简单概念的示例:

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

上面的代码定义了这个integral概念。如你所见,它看起来与其他template<>构造类似。

这使用了一种条件,我们可以通过一个众所周知的type trait(来自C ++ 11 / C ++ 14)-进行计算std::is_integral_v。它产生truefalse取决于输入模板参数。

我们还可以使用requires表达式定义另一个:

template <typename T>
concept ILabel = requires(T v)
{
    {v.buildHtml()} -> std::convertible_to<std::string>;
};

我们定义了一个概念,要求类型T的对象具有一个称为buildHtml()的成员函数,该成员函数返回可转换为std::string的值。

这两个例子应该让你有点感觉了。让我们尝试在一些真实的代码中使用它们。


如何使用concept

在最常见的情况之一中,对于小型函数模板,有以下语法:

template <typename T>
requires CONDITION
void DoSomething(T param) { }

还可以将其requires clause用作函数声明的最后一部分:

template <typename T>
void DoSomething(T param) requires CONDITION
{ 
    
}

requires允许我们在输入模板参数上指定各种要求。

让我们看一个简单的函数模板,该模板计算输入容器的平均值。

#include <numeric>
#include <vector>
#include <iostream>
#include <concepts>

template <typename T> 
requires std::integral<T> || std::floating_point<T>
constexpr double Average(std::vector<T> const &vec) {
    const double sum = std::accumulate(vec.begin(), vec.end(), 0.0);        
    return sum / vec.size();
}

int main() {
    std::vector ints { 1, 2, 3, 4, 5};
    std::cout << Average(ints) << '\n';                                      
}

上面的源代码使用了标准库(std::integralstd::floating_point)的两个概念,并将它们组合在一起。


优势之一:更友好的编译器错误提示

如果使用前面的示例并编写:

std::vector strings {"abc", "xyz"};
auto test = Average(strings); 

会得到:

<source>:23:24: error: no matching function for call to 'Average(std::vector<const char*, std::allocator<const char*> >&)'
   23 |     auto test = Average(strings);
      |                 ~~~~~~~^~~~~~~~~
<source>:10:18: note: candidate: 'template<class T>  requires (integral<T>) || (floating_point<T>) constexpr double Average(const std::vector<T>&)'
   10 | constexpr double Average(std::vector<T> const &vec) {
      |                  ^~~~~~~

可以看到模板实例化失败,因为您的模板参数-std::string不是整数或浮点数。

通常,使用模板,在concept特性出现之前,你可能会获得一些关于某些失败操作的长信息。


预定义的concept

这是我们在C ++ 20中使用<concepts>标头获得的预定义概念的列表:

核心cecept

备注

same_as


derived_from


convertible_to


common_reference_with


common_with


integral


signed_integral


unsigned_integral


floating_point


assignable_from


swappableswappable_with


destructible


constructible_from


default_initializable


move_constructible


copy_constructible


比较concept

备注

boolean-testable

一个类型可以在布尔测试用例中使用

equality_comparable/equality_comparable_with


totally_ordered/totally_ordered_with

定义于 <compare>

three_way_comparable/three_way_comparable_with


对象concept

备注

movable


copyable


semiregular

可以复制,移动,交换和默认构造类型

regular

一种类型是既semiregularequality_comparable

可调用的concept

备注

invocable/regular_invocable


predicate


relation

指定二进制关系

equivalence_relation


strict_weak_order



代码简化

在C ++ 20中,可以使模板代码超级简单。

  • 缩写功能模板
  • 受约束的auto
  • concept的简洁语法

例如:

template <typename T>
void print(const std::vector<T>& vec) {
    for (size_t i = 0; auto& elem : vec)
        std::cout << elem << (++i == vec.size() ? "\n" : ", ");
}

我们可以将其简写为:

void print2(const std::vector<auto>& vec) {
    for (size_t i = 0; auto& elem : vec)
        std::cout << elem << (++i == vec.size() ? "\n" : ", ");
}

上面使用了unconstrained auto。通常,你可以编写:

auto func(auto param) { }

他扩展为:

template <typename T>
auto func(T param) { }

它看起来与我们使用C ++ 14和泛型lambdas(Lambda Week:Going Generic)相似。

另外,我们还可以使用受约束的auto

void print3(const std::ranges::range auto& container) {
	for (size_t i = 0; auto && elem : container)
		std::cout << elem << (++i == container.size() ? "\n" : ", ");
};

print3,消除了传递向量的需要,并限制了所有范围。

这里我们有:

auto func(concept auto param) { }

转换为:

template <typename T>
requires concept<T>
auto func(T param) { }

而且,不用template <typename T> requires...,可以简写:

template <std::integral T>
auto sum(const std::vector<T>& vec) {
    // return ...;
}

requires关键字

requires关键字是concept功能最强大的项目之一。它有两种形式:

    • the requires clause - 类似 requires std::integral<T>
    • the requires expression.

最后一个非常灵活,可以指定非常高级的约束。在介绍中,您已经看到了一种检测buildHtml()成员函数的情况。下面是另一个例子:

template<typename T>
concept has_string_data_member = requires(T v) { 
    { v.name_ } -> std::convertible_to<std::string>; 
};

struct Person {
    int age_ { 0 };
    std::string name_;
};

struct Box {
    double weight_ { 0.0 };
    double volume_ { 0.0 };
};

int main() {
    static_assert(has_string_data_member<Person>);
    static_assert(!has_string_data_member<Box>);
}

从上面可以看到,我们可以编写requires(T v),从现在开始,我们可以建设我们拥有type的值T,然后可以列出可以使用的操作。

另一个例子:

template <typename T>
concept Clock = requires(T c) { 
    c.start();  
    c.stop();
    c.getTime();
  };

以上概念限制了Clock的“接口”。我们要求它具有三个成员函数,但是我们不指定它们返回的类型。


编译器支持

从2021年5月开始,可以在所有主要编译器上使用concept:GCC(自10.0起),Clang(10.0)和MSVC(2019 16.3基本支持,16.8约束自动,16.9缩写功能模板,请参见注释)。只需记住对C ++ 20标准使用适当的flag - -std=c++20/-std=c++2a(Clang / GCC)或/std:c++latest(MSVC)。


Tags:

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

欢迎 发表评论:

最近发表
标签列表