【问题标题】:C++20 Concepts : Which template specialization gets chosen when the template argument qualifies for multiple concepts?C++20 概念:当模板参数符合多个概念时,选择哪个模板特化?
【发布时间】:2020-05-14 02:26:38
【问题描述】:

给定:

#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 在这种情况下,概念经过精心设计,因此它们以直观的方式相互包含
  • @GuillaumeRacicot 很好,我只是评论说“这是合法的,因为编译器接受了它”这个结论可以说是误导。我并没有说这是不合法的。

标签: c++ language-lawyer c++20 c++-concepts


【解决方案1】:

这是因为概念可以比其他概念更专业,有点像模板如何为自己排序。这叫partial ordering of constraints

在概念的情况下,当它们包含等效约束时,它们会相互包含。例如,下面是 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。所以从某种意义上说,有符号积分比积分“更受约束”。

标准是这样写的:

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

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

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

来自[temp.constr.order]/1

一个约束P包含一个约束Q当且仅当,对于每个析取子句PiP 的析取范式,Pi 包含所有连取子句QjQ的合取范式中,其中

  • 分离子句Pi包含一个连接子句Qj当且仅当存在一个原子Pia 中的约束 Pi 中存在原子约束 QjbQj 中,使得 Pia 包含 Q jb,和

  • 一个原子约束 A 包含另一个原子约束 B 当且仅当 AB 相同时使用[temp.constr.atomic] 中描述的规则。

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

【讨论】:

    【解决方案2】:

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

    这始于将约束分解为其原子组件的概念,该过程称为constraint normalization。它太大且太复杂,无法在这里介绍,但基本思想是,约束中的每个表达式都递归地分解为其原子概念部分,直到您到达不是概念的组件子表达式。

    鉴于此,让我们看看integralsigned_integral 的概念are defined

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

    integral 的分解就是is_integral_vsigned_integral的分解为is_integral_v &amp;&amp; is_signed_v

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

    接下来,我们来对受约束的实体进行排序:

    声明 D1 至少与声明 D2 一样受到约束

    • D1 和 D2 都是受约束的声明,并且 D1 的相关约束包含 D2 的约束;或
    • D2 没有关联的约束。

    因为signed_integral 包含integral,所以&lt;signed_integral&gt; wrapper“至少与&lt;integral&gt; wrapper 一样受约束”。但是,由于包含不可逆,因此反过来是不正确的。

    因此,符合“更受约束”实体的规则:

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

    由于&lt;integral&gt; wrapper 至少不像&lt;signed_integral&gt; 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_vstd::is_integral_v 定义相同,但因为它不是一个概念,C++ 的包含规则无法通过它来确定它们是否相同。

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

    【讨论】:

      【解决方案3】:

      Partial_ordering_of_constraints

      如果可以证明 P 隐含 Q 直到 P 和 Q 中原子约束的恒等式,则称约束 P 包含约束 Q。

      包含关系定义了约束的偏序,用于确定:

      • 重载决议中非模板函数的最佳可行候选者
      • 重载集中非模板函数的地址
      • 模板模板参数的最佳匹配
      • 类模板特化的部分排序
      • 函数模板的部分排序

      而概念std::signed_integral 包含std::integral&lt;T&gt; 概念:

      template < class T >
      concept signed_integral = std::integral<T> && std::is_signed_v<T>;
      

      所以你的代码没问题,因为std::signed_integral 更“专业”。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-18
        • 2021-09-04
        相关资源
        最近更新 更多