存在包含关系的concepts
两个concepts可以具有包含关系。
也就是说,可以指定一个concepts来限制一个或多个其他concepts。
好处是,当两个约束都满足时,重载解析优先选择受约束较多的泛型代码,而不是约束较少的泛型代码。
例如,我们引入以下两个concepts
template<typename T>
concept GeoObject = requires(T obj) {
{ obj.width()}→> std::integral;
{ obj.height()} →> std::integral;
obj.draw();
};
template<typename T>
concept ColoredGeoObject =
GeoObject<T>&&
requires(T obj) {
obj.setColor(Color{});
{ obj.getColor()}→>std::convertible_to<Color>;
};
ColoredGeoObject concepts明确包含concepts GeoObject,因为它明确地形成了类型 T 也必须满足concepts GeoObject 的约束。
因此,当两个concepts的重载模板并且两者都满足时,我们不会产生歧义。重载分辨率首选包含其他(s)的concepts:
template<GeoObject T>
void process(T)
{
...
}
template<ColoredGeoObject T>
void process(T)
{
...
}
约束包含仅在使用concepts时才有效
。当一个concepts/约束比另一个concepts/约束更特殊时,不会自动包含。
约束和concepts不仅仅基于需求。
例子:
template<typename T>
concept GeoObject = requires(T obj) {
obj.draw(); };
template<typename T>
concept Cowboy = requires(T obj) {
obj.draw();
obj = obj;
};
假设我们为GeoObject和Cowboys重载一个函数模板:
template<GeoObject T>
void print(T) {
...
}
template<Cowboy T>
void print(T) {
...
}
对于Circle或Rectangle,我们不希望它具有draw()成员函数,print()调用更倾向于将print()用于Cowboy,因为Cowboyconcepts更特殊。
我们希望看到有两个print()函数在本例中发生冲突。
template<typename T>
requires std::is_convertible_v<T,int>
void print(T) {
...
}
template<typename T>
requires (std::is_convertible_v<T, int> && sizeof(int) >=4)
void print(T){
...
}
print(42);
当使用concepts代替时,下面的代码可以工作:
template<typename T>
requires std::convertible_to<T,int>
void print(T) {
...
}
template<typename T>
requires (std::convertible_to<T,int> && sizeof(int)>=4)
void print(T){
...
}
print(42);
这种行为的一个原因是需要编译时间来详细处理concepts之间的依赖关系。标准库提供的concepts经过精心设计,以便在有意义时包含其他concepts。
例如:std::random access range包含std::bidirectional range,两者都包含std::forward range,三者都包含std:: input range,它们都包含std::range。
然而,std::size range只包含std::range,其他的都不包含。Std::regular包含Std::semiregular和两者都包含Std::copyable和Std::default initializabl
间接的包含
约束甚至可以间接包含在内。这意味着,重载解析仍然可以选择一种重载,尽管它们的约束没有根据彼此定义
例如,假设您已经定义了以下两个concepts
template<typename T>
concept RgSwap = std::ranges::input_range<T> && std: :swappable<T>
; template<typename T>
concept ContCopy = std::ranges::contiguous_range<T> && std::copyable<T>;
现在,当我们为这两个concepts重载两个函数并传递一个符合这两个concepts的对象时,这就没有歧义了
template<RgSwap T>
void foo1(T){
std::cout<<"foo1(RgSwap)\n";
}
template<ContCopy T>
void foo1(T){
std::cout <<"foo1(ContCopy)\n";
}
foo1(std::vector<int>{});
原因是ContCopy包含RgSwap,因为:
concepts连续范围定义为concepts输入范围(它意味着随机访问范围,它意味着双向范围,它意味着向前范围,它意味着输入范围)。concepts可复制是根据concepts可交换来定义的(它意味着可移动,意味着可交换)。
然而,使用下面的声明,当两个concepts都适合时,我们会得到一个歧义
template<typename T>
concept RgSwap = std::ranges::sized_range<T> && std::swappable<T>;
template<typename T>
concept ContCopy = std::ranges::contiguous_range<T> && std::copyable<T>;
原因是相邻范围的concepts既不意味着大小范围,大小范围的concepts也不意味着连续范围。
此外,对于下列声明,没有concepts包含其他concepts
template<typename T>
concept RgCopy = std::ranges::input_range<T> && std::copyable<T>;
template<typename T>
concept ContMove = std::ranges::contiguous_range<T> && std::movable<T>;
一方面,ContMove更受约束,因为contiguous range意味着输入范围;然而,另一方面,RgCopy更受约束,因为copyable意味着可移动。为了避免混淆,不要对相互包含的concepts做太多假设。如果有疑问,请详细说明所需的所有concepts。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情”