更多互联网新鲜资讯、工作奇淫技巧关注原创【飞鱼在浪屿】(日更新)
Concepts 是编写模板的革命性方法!它们可以对模板参数施加约束,以提高代码的可读性,加快编译时间并提供更好的错误消息。
什么是Concept ?
简而言之,一个Concept是在编译时对模板参数进行评估的一组约束。可以将它们用于类模板和函数模板,以控制函数重载和部分专业化。
C ++ 20为我们提供了语言支持(新关键字- requires,concept)以及标准库中的一组预定义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。它产生true或false取决于输入模板参数。
我们还可以使用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::integral和std::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 | 一种类型是既semiregular与equality_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)。
本文暂时没有评论,来添加一个吧(●'◡'●)