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

Bar*_*rry 17 c++ c++-concepts c++20

考虑这个例子:

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

调用是foo<int>()不明确的,还是约束C1<T> && C2<T>包含C1<T>

Bar*_*rry 19

是.只有概念可以包含在内.调用foo<int>是模棱两可的,因为这两个声明都没有"至少像另一个那样受限制".

但是,如果C1和s C2都是concepts而不是inline constexpr bools,那么foo()返回0的声明至少会像foo()返回的声明那样受到约束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); };
}
Run Code Online (Sandbox Code Playgroud)

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

含有由约束不同的可行候选的过载设置X::Fooable,Y::Fooable以及Z::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是定义如下的约束:

  • 表达式(E)的正常形式是E的正常形式.
  • 表达式E1 ||的正常形式 E2是E1和E2的正常形式的分离.
  • 表达式E1 && E2的正常形式是E1和E2的正常形式的结合.
  • 一个的正常形态ID表达的C型<A 1,A 2,..., A N>,其中C名称的概念,是正常形式约束表达的C,取代甲后1, A 2,...,A n表示每个原子约束中参数映射中C的相应模板参数.如果任何此类替换导致无效的类型或表达,则该程序格式不正确; 无需诊断.[...]
  • 任何其他表达式的正常形式是其表达式E为的原子约束,E其参数映射是标识映射.

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

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

因此,下标表明这些实际上不是相同的原子约束.C1<T>1C1<T>2不同.规则不是令牌等价!因此,第一个fooC1<T>不归入第二fooC1<T>,反之亦然.

因此,含糊不清.

另一方面,如果我们有:

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

第一个函数的约束是D1<T> && D2<T>.第三子弹给我们的结合D1<T>D2<T>.第四个子弹然后引导我们替换概念本身,所以第一个归一化为true1,第二个归一化为true2.同样,下标表示正在引用哪个 true.

第二个函数的约束是D1<T>,将(第4个子弹)标准化为true1.

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

  • @Casey _expression_是非终结语法. (3认同)
  • 我认为这个论点是站不住脚的,因为“相同表达”一词没有规范的定义。我理解 P717 的意图,并且我们将语义挂在“相同表达”的解释上,但我希望看到它变得更加具体。标准中“相同表达式”的其他使用是不规范的,至少 [class.copy.elision]/1 中的注释似乎与这种解释相矛盾。 (2认同)
  • @Casey不是“相同的表达”,而是“相同的_expression_”-是的,我不会反对在这方面更明确的措词。 (2认同)