多个 concepts 之间的包含关系

568 阅读3分钟

存在包含关系的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 天,点击查看活动详情