约束包含仅适用于概念吗?

时间:2018-08-28 16:20:58

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

考虑以下示例:

 $('.nav-link.active').on('click', function(){
//This targets only li elements having active class
    $(this).toggleClass('active');
    $('#' + this.hash.substr(1).toLowerCase()).toggleClass('active');
    return false;
});

调用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<int>()包含C1<T> && C2<T>

1 个答案:

答案 0 :(得分:17)

是的。只能包含 概念。对foo<int>的调用是模棱两可的,因为这两个声明都不“相互约束”。

但是,如果C1C2都是concept而不是inline constexpr bool,则返回{{1 }}至少与返回foo()的{​​{1}}的声明一样受约束,对0的调用将有效并返回foo()。这是相对于任意布尔常量表达式更倾向于使用概念作为约束的原因之一。


背景

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

1
     

foo<int>等效于0,尽管它们表示完全不同的含义(由于在不同的命名空间中定义)。这种偶然的等效性是有问题的:具有这两个概念所约束的函数的重载集将是模棱两可的。

     

当一个概念偶然地完善了另一个概念时,这个问题就更加恶化了。

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::FooableY::Fooablenamespace 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::Fooable约束的候选。几乎可以肯定,这不是程序员想要的。


标准参考

包含规则位于[temp.constr.order]/1.2中:

  

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

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

  

原子约束由表达式Y::Fooable以及从Z::Fooable中出现的模板参数到涉及受约束实体的模板参数的模板参数的映射形成,称为参数映射([temp.constr.decl])。 [注意:原子约束是通过约束归一化形成的。 Z::Fooable既不是逻辑E表达式,也不是逻辑E表达式。 -注释]

     

如果两个原子约束是由相同的表达式形成的,并且根据[temp.the]中描述的表达式的规则,参数映射的目标是等效的,则它们是相同的。 over.link]。

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

  

表达式 E正常形式是如下定义的约束:

     

对于E的第一个重载,约束为E,因此为了对其进行规范化,我们得到了E 1 的普通形式的合取。和foo 1 ,然后就完成了。同样,对于C1<T> && C2<T>的第二次重载,约束为C1<T> 2 ,这是它自己的标准形式。

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

因此,下标表明它们实际上不是相同的原子约束。 foo 1 C1<T> 2 不同。规则不是令牌等效!因此,第一个C1<T>的{​​{1}}不包含第二个C1<T>的{​​{1}},反之亦然。

因此,模棱两可。

另一方面,如果我们有:

C1<T>

第一个函数的约束为foo。第三个项目符号使我们将C1<T>foo结合在一起。然后,第四个项目符号将我们替换为概念本身,因此第一个项目符号归一化为C1<T> 1 ,第二个项目归一化为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; } 2 。再次,下标指示哪个 D1<T> && D2<T>被引用。

第二个函数的约束为D1<T>,它将(第4个项目符号)归一化为D2<T> 1

现在,true 1 的确与true 1 相同,因此这些约束被认为是相同的。结果,true包含了D1<T>,而true是一个明确的调用,它返回了true