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

Lew*_*man 24 c++ language-lawyer c++-concepts c++20

鉴于:

#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;
}
Run Code Online (Sandbox Code Playgroud)

从上面的代码中,int符合std::integralstd::signed_integral概念。

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

好的,这是合法的,足够公平,但为什么std::signed_integral选择而不是std::integral?当多个概念符合模板参数的条件时,标准中是否定义了任何规则来选择模板特化?

Gui*_*cot 16

这是因为概念可以比其他概念更专业,有点像模板本身的排序方式。这称为约束的偏序

在概念的情况下,当它们包含等效约束时,它们相互包含。例如,以下是实现方式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>;
Run Code Online (Sandbox Code Playgroud)

规范化约束编译器将约束表达式归结为:

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>;
Run Code Online (Sandbox Code Playgroud)

在这个例子中,完全signed_integral隐含integral。所以从某种意义上说,有符号积分比积分“更受约束”。

标准是这样写的:

来自[temp.func.order]/2(强调我的):

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

这意味着如果一个模板有多种可能的替代,并且都从偏序中选择,它将选择最受约束的模板。

[temp.constr.order]/1

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

  • 一个析取子句P i包含一个连接子句Q j当且仅当在P i 中存在一个原子约束P ia并且在Q j 中存在一个原子约束Q jb使得P ia包含Q jb,并且

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

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


Nic*_*las 12

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

这始于将约束分解为其原子组件的概念,这个过程称为约束规范化。在这里讨论太大而且太复杂,但基本思想是约束中的每个表达式都被分解为它的原子概念部分,递归地,直到您到达一个不是概念的组件子表达式。

鉴于此,让我们看看integralsigned_integral概念如何定义的

模板概念积分 = is_integral_v; 模板概念 signed_integral = 积分 && is_signed_v;

的分解integral只是is_integral_v。的分解signed_integralis_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>;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,my_signed_integral 不会包含std::integral. 即使my_is_integral_v定义与 相同std::is_integral_v,因为它不是一个概念,C++ 的包含规则无法通过它来确定它们是否相同。

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