【问题标题】:Does constraint subsumption only apply to concepts?约束包含仅适用于概念吗?
【发布时间】:2023-03-27 07:24:01
【问题描述】:

考虑这个例子:

template <typename T> inline constexpr bool C1 = true;    
template <typename T> inline constexpr bool C2 = true;

template <typename T> requires C1<T> && C2<T> 
constexpr int foo() { return 0; }

template <typename T> requires C1<T> 
constexpr int foo() { return 1; }

constexpr int bar() {
    return foo<int>();
}

调用 foo&lt;int&gt;() 是否模棱两可,还是约束 C1&lt;T&gt; &amp;&amp; C2&lt;T&gt; 包含 C1&lt;T&gt;

【问题讨论】:

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


    【解决方案1】:

    是的。 只有个概念可以被归入。对foo&lt;int&gt; 的调用是模棱两可的,因为这两个声明都“至少与另一个声明一样受约束”。

    但是,如果 C1C2 都是 concepts 而不是 inline constexpr bools,那么返回 0foo() 的声明将至少与返回1foo() 和对foo&lt;int&gt; 的调用将有效并返回0。这是首选使用概念作为约束而不是任意布尔常量表达式的原因之一。


    背景

    这种差异的原因(概念包含,任意表达式不包含)最好在Semantic constraint matching for concepts 中表达,值得完整阅读(我不会在这里复制所有论点)。但以论文为例:

    namespace X {
      template<C1 T> void foo(T);
      template<typename T> concept Fooable = requires (T t) { foo(t); };
    }
    namespace Y {
      template<C2 T> void foo(T);
      template<typename T> concept Fooable = requires (T t) { foo(t); };
    }
    

    X::Fooable 等同于Y::Fooable,尽管它们的含义完全不同(由于在不同的命名空间中定义)。这种偶然的等价是有问题的:具有受这两个概念约束的函数的重载集将是模棱两可的。

    当一个概念偶然提炼其他概念时,这个问题会更加严重。

    namespace Z {
      template<C3 T> void foo(T);
      template<C3 T> void bar(T);
      template<typename T> concept Fooable = requires (T t) {
        foo(t);
        bar(t);
      };
    }
    

    包含分别受X::FooableY::FooableZ::Fooable 约束的不同可行候选者的重载集将始终选择受Z::Fooable 约束的候选者。这几乎肯定不是程序员想要的。


    标准参考

    包含规则在[temp.constr.order]/1.2:

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

    原子约束在[temp.constr.atomic]中定义:

    原子约束由表达式 E 和从出现在 E 中的模板参数到涉及受约束实体的模板参数的模板参数的映射组成,称为参数映射([temp.constr.decl])。 [ 注意:原子约束是由约束规范化形成的。 E 绝不是逻辑 AND 表达式,也不是逻辑 OR 表达式。 — 尾注 ]

    如果两个原子约束由相同的 表达式 构成,并且根据 [temp. over.link]。

    这里的关键是原子约束是形成的。这是这里的关键点。在[temp.constr.normal]:

    表达式E正规形式是一个约束,定义如下:

    对于foo的第一个重载,约束是C1&lt;T&gt; &amp;&amp; C2&lt;T&gt;,所以为了规范化它,我们得到C1&lt;T&gt;1C2&lt;T&gt;1 然后我们就完成了。同样,对于foo 的第二个重载,约束是C1&lt;T&gt;2,这是它自己的正常形式。

    使原子约束相同的规则是它们必须由相同的表达式(源级构造)形成。虽然这两个函数都有一个使用标记序列C1&lt;T&gt; 的原子约束,但它们在源代码中是不同的文字表达式

    因此,下标表明它们实际上不是同一个原子约束。 C1&lt;T&gt;1C1&lt;T&gt;2 不同。规则不是令牌等价!所以第一个fooC1&lt;T&gt;不包含第二个fooC1&lt;T&gt;,反之亦然。

    因此,模棱两可。

    另一方面,如果我们有:

    template <typename T> concept D1 = true;    
    template <typename T> concept D2 = true;
    
    template <typename T> requires D1<T> && D2<T> 
    constexpr int quux() { return 0; }
    
    template <typename T> requires D1<T> 
    constexpr int quux() { return 1; }
    

    第一个函数的约束是D1&lt;T&gt; &amp;&amp; D2&lt;T&gt;。第三个项目符号给了我们D1&lt;T&gt;D2&lt;T&gt; 的连词。然后第 4 个项目符号将我们代入概念本身,因此第一个项目符号规范化为 true1,第二个项目符号规范化为 true2。同样,下标表示 哪个 true 被引用。

    第二个函数的约束是D1&lt;T&gt;,它将(第4个项目符号)归一化为true1

    现在,true1 确实是与 true1 相同的表达式,因此这些约束被认为是相同的。因此,D1&lt;T&gt; &amp;&amp; D2&lt;T&gt; 包含D1&lt;T&gt;,而quux&lt;int&gt;() 是一个返回0 的明确调用。

    【讨论】:

    • 我认为这个论点很弱,因为“相同表达”一词没有规范定义。我理解 P717 的意图,并且我们将语义悬挂在“相同表达”的这种解释上,但我希望看到它更具体。标准中“相同表达”的其他用法是非规范性的,至少 [class.copy.elision]/1 中的注释似乎与这种解释相矛盾。
    • @Casey 这不是“相同的表达方式”,而是“相同的 表达方式”——但我不会反对在这方面更明确的措辞。
    • @Casey expression 是非终结符语法。
    • @T.C.是的,谢谢两位。所以在工作草案中没有出现其他“相同的表达式”——其他的都是“相同的表达式”。现在我们只有“same ”没有明确含义的问题。
    • @Casey 我认为它具有明显的英语含义(对于“明显”的某些定义):)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-11
    • 1970-01-01
    • 1970-01-01
    • 2021-12-26
    • 1970-01-01
    • 2022-09-29
    相关资源
    最近更新 更多