给出:
#include <concepts>
#include <iostream>
template<class T>
struct wrapper;
template<std::signed_integral T>
struct wrapper<T>
{
wrapper() = default;
void print()
{
std::cout << "signed_integral" << std::endl;
}
};
template<std::integral T>
struct wrapper<T>
{
wrapper() = default;
void print()
{
std::cout << "integral" << std::endl;
}
};
int main()
{
wrapper<int> w;
w.print(); // Output : signed_integral
return 0;
}
从上面的代码中,int
符合std::integral
和std::signed_integral
的概念。
令人惊讶的是,它可以在GCC和MSVC编译器上编译并打印“ signed_integral”。我原以为它会因“已经定义了模板专业化”而失败,并出现错误。
好的,这很合法,很公平,但是为什么选择std::signed_integral
而不是std::integral
?标准中是否定义了规则,当多个概念都适合模板参数时选择了哪种模板专业化?
答案 0 :(得分:16)
这是因为概念可以比其他概念更专业,有点像模板如何对其进行排序。这称为partial ordering of constraints
在概念的情况下,当它们包含等效约束时,它们会彼此包含。例如,以下是std::integral
和std::signed_integral
的实现方式:
template<typename T>
concept integral = std::is_integral_v<T>;
template<typename T> // v--------------v---- Using the contraint defined above
concept signed_integral = std::integral<T> && std::is_signed_v<T>;
归一化约束条件,编译器将约束表达式简化为:
template<typename T>
concept integral = std::is_integral_v<T>;
template<typename T>
concept signed_integral = std::is_integral_v<T> && std::is_signed_v<T>;
在此示例中,signed_integral
完全意味着integral
。因此,从某种意义上说,有符号积分比积分“更受约束”。
标准这样写:
来自[temp.func.order]/2(重点是我):
偏序选择两个功能模板中哪个更合适 通过依次转换每个模板(请参见下一节)并使用函数类型执行模板参数推导来实现比其他模板更专业的目的。 推导过程确定一个模板是否比另一个模板更专业。 如果是这样,则更专业的模板是部分订购过程选择的模板。 如果两个推论都成功,则偏序排序将选择约束更严格的模板,如[temp.constr.order]中的规则所述。
这意味着,如果一个模板有多种可能的替换方式,并且都从偏序中选择了两者,那么它将选择约束最大的模板。
一个约束 P包含一个约束 Q ,当且仅当,对于其中的每个析取子句 P i P , P i 的析取范式包含每个析取项 Q j 以 Q 的合取范式表示,其中
一个析取子句 P i 包含一个析取子句 Q j 在 P i 中存在原子约束 P ia ,对此存在原子约束 Q j 中的> jb ,这样 P ia 包含 Q jb 和
原子约束 A 包含另一个原子约束 B ,当且仅当 A 和 B 使用[temp.constr.atomic]中描述的规则是相同的。
这描述了编译器用来对约束进行排序的归类算法,以及概念。
答案 1 :(得分:12)
C ++ 20具有一种机制来确定何时一个特定的受约束实体比另一个受约束的实体“受约束更多”。这不是一件简单的事情。
这始于将约束分解成其原子成分的过程,该过程称为constraint normalization。它太大且太复杂,因此无法在此处进行介绍,但基本思想是将约束中的每个表达式递归地分解为其原子概念片段,直到到达不是概念的组件子表达式为止。
因此,让我们看一下are defined的integral
和signed_integral
概念:
模板 概念积分= is_integral_v; 模板 概念signed_integral =整数&& is_signed_v;
integral
的分解仅为is_integral_v
。 signed_integral
的分解为is_integral_v && is_signed_v
。
现在,我们来讨论constraint subsumption的概念。这有点复杂,但是基本思想是,如果C1的分解包含C2中的每个子表达式,则约束C1被称为“包含”约束C2。我们可以看到integral
并不包含signed_integral
,而是signed_integral
确实包含了{em} ,因为它包含了integral
所做的一切。
接下来,我们来订购受约束的实体:
如果,声明D1至少与声明D2一样受约束
D1和D2都是约束声明,D1的关联约束包含D2的约束;或
- D2没有关联的约束。
由于integral
包含signed_integral
,因此integral
与<signed_integral> wrapper
“至少受约束”。但是,相反的情况并非如此,因为这种归纳是不可逆的。
因此,根据“更受限”实体的规则:
当D1至少受D2约束,而D2至少受D1约束时,声明D1比另一个声明D2受约束更大。
由于<integral> wrapper
的约束至少不像<integral> wrapper
那样,因此后者被认为比前者更受约束。
因此,当两者都适用时,约束更严格的声明将获胜。
请注意,遇到非<signed_integral> wrapper
的表达式时,约束包含规则停止。因此,如果您这样做:
concept
在这种情况下,template<typename T>
constexpr bool my_is_integral_v = std::is_integral_v<T>;
template<typename T>
concept my_signed_integral = my_is_integral_v<T> && std::is_signed_v<T>;
不会包含my_signed_integral
。尽管std::integral
的定义与my_is_integral_v
相同,但由于它不是一个概念,因此C ++的包含规则无法通过它窥视确定它们是否相同。
因此,包含规则鼓励您从原子概念的操作中构建概念。
答案 2 :(得分:3)
使用Partial_ordering_of_constraints
如果可以证明在P和Q中的原子约束相同之前,约束P可以包含约束Q。
和
包含关系定义约束的部分顺序,该顺序用于确定:
- 过载解决方案中非模板函数的最佳可行候选人
- 重载集中非模板函数的地址
- 模板模板参数的最佳匹配
- 班级模板专业化的部分排序
- 功能模板的部分排序
概念std::signed_integral
包含std::integral<T>
概念:
template < class T >
concept signed_integral = std::integral<T> && std::is_signed_v<T>;
所以您的代码还可以,因为std::signed_integral
更加“专业化”。