C ++ 20概念:当template参数符合多个概念时,选择哪种模板专业化?


23

鉴于:

#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::integralstd::signed_integral概念。

出人意料的是,这会在GCC和MSVC编译器上编译并打印“ signed_integral”。我原以为它会因“已定义模板专业化”而失败,并出现错误。

好的,这很合法,很公平,但是为什么std::signed_integral选择而不是std::integral?当多个概念都适合模板参数时,标准中是否定义了选择哪种模板专业化的规则?


我不会仅仅因为编译器接受它就可以说是合法的,尤其是在采用它的早期阶段。
斯拉瓦

@Slava在这种情况下,概念是经过精心设计的,因此它们以直观的方式相互包含
Guillaume Racicot

@GuillaumeRacicot很好,我只是说结论“合法,因为编译器接受了”的结论令人误解。我没有说这是不合法的。
斯拉瓦

Answers:


14

这是因为概念可以比其他概念更专业,就像模板如何对其进行排序。这称为约束的部分排序

在概念的情况下,当它们包括等效约束时,它们会彼此包含。例如,以下是实现方法std::integralstd::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。因此,从某种意义上说,有符号积分比积分“更受约束”。

标准这样写:

[温度顺序] / 2(强调我的):

偏序排序通过依次转换每个模板(请参见下一段)并使用函数类型执行模板自变量推导来选择两个功能模板中的哪个模板比另一个模板更专业。推导过程确定其中一个模板是否比另一个模板更专业。如果是这样,则更专业的模板是部分订购过程选择的模板。 如果两个推论都成功,则部分排序将选择更受约束的模板,如[temp.constr.order]中的规则所述。

这意味着,如果一个模板有多种可能的替换方式,并且都从局部排序中选择了两者,那么它将选择约束最大的模板。

[temp.constr.order] / 1

约束P涵括的约束Q当且仅当,对于每一个析取子句P 在的析取范式PP 涵括每结膜子句Q Ĵ中的合取范式Q,其中

  • 分离性子句P 涵括合用子句Q Ĵ当且仅当存在一个原子约束P IAP 存在用于其的原子约束Q JBQ Ĵ使得P IA涵括Q JB,和

  • 一个原子约束涵括另一原子约束当且仅当使用中所描述的规则是相同的[temp.constr.atomic]

这描述了编译器用来对约束进行排序的包含算法,因此也对概念进行了描述。


2
看起来像是您在段落的中间尾随了……
ShadowRanger

11

C ++ 20具有一种机制来确定何时一个特定的受约束实体比另一个受约束的实体“受约束更多”。这不是一件简单的事情。

这始于将约束分解成其原子成分的概念,这一过程称为约束规范化。它太大且太复杂,无法在此处介绍,但基本思想是将约束中的每个表达式递归地分解为其原子概念片段,直到到达不是概念的组件子表达式为止。

因此,让我们来看一下integralsigned_integral概念的定义方式

template<class T>
  concept integral = is_integral_v<T>;
template<class T>
  concept signed_­integral = integral<T> && is_signed_v<T>;

的分解integralis_integral_v。分解signed_integralIS is_integral_v && is_signed_v

现在,我们来讨论约束包含的概念。这有点复杂,但是基本思想是,如果C1的分解包含C2中的每个子表达式,则约束C1被称为“包含”约束C2。我们可以看到,integral不归入signed_integral,但signed_integral 确实归入integral,因为它包含的一切integral一样。

接下来,我们来订购受约束的实体:

如果* D1和D2都是约束声明,并且D1的关联约束包含D2的约束,则声明D1至少与声明D2一样约束。或* D2没有关联的约束。

因为signed_integral包含integral,所以<signed_integral> wrapper与至少“一样受约束” <integral> wrapper。但是,相反情况并非如此,因为这种归类是不可逆的。

因此,根据“更受限”实体的规则:

当D1至少与D2一样约束,并且D2至少不像D1那样约束时,声明D1比另一个声明D2更受约束。

由于的<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定义与相同std::is_integral_v,因为它不是一个概念,所以C ++的包含规则无法通过它窥视确定它们是否相同。

因此,包含规则鼓励您从对原子概念的操作中构建概念。


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更“专业化”了。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.