是的。 只有个概念可以被归入。对foo<int> 的调用是模棱两可的,因为这两个声明都“至少与另一个声明一样受约束”。
但是,如果 C1 和 C2 都是 concepts 而不是 inline constexpr bools,那么返回 0 的 foo() 的声明将至少与返回1 的foo() 和对foo<int> 的调用将有效并返回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::Fooable、Y::Fooable 和Z::Fooable 约束的不同可行候选者的重载集将始终选择受Z::Fooable 约束的候选者。这几乎肯定不是程序员想要的。
标准参考
包含规则在[temp.constr.order]/1.2:
一个原子约束 A 包含另一个原子约束 B 当且仅当 A 和 B 相同使用 [temp.constr.atomic] 中描述的规则。
原子约束在[temp.constr.atomic]中定义:
原子约束由表达式 E 和从出现在 E 中的模板参数到涉及受约束实体的模板参数的模板参数的映射组成,称为参数映射([temp.constr.decl])。 [ 注意:原子约束是由约束规范化形成的。 E 绝不是逻辑 AND 表达式,也不是逻辑 OR 表达式。 — 尾注 ]
如果两个原子约束由相同的 表达式 构成,并且根据 [temp. over.link]。
这里的关键是原子约束是形成的。这是这里的关键点。在[temp.constr.normal]:
表达式E的正规形式是一个约束,定义如下:
对于foo的第一个重载,约束是C1<T> && C2<T>,所以为了规范化它,我们得到C1<T>1和C2<T>1 然后我们就完成了。同样,对于foo 的第二个重载,约束是C1<T>2,这是它自己的正常形式。
使原子约束相同的规则是它们必须由相同的表达式(源级构造)形成。虽然这两个函数都有一个使用标记序列C1<T> 的原子约束,但它们在源代码中是不同的文字表达式。
因此,下标表明它们实际上不是同一个原子约束。 C1<T>1 与 C1<T>2 不同。规则不是令牌等价!所以第一个foo的C1<T>不包含第二个foo的C1<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; }
第一个函数的约束是D1<T> && D2<T>。第三个项目符号给了我们D1<T> 和D2<T> 的连词。然后第 4 个项目符号将我们代入概念本身,因此第一个项目符号规范化为 true1,第二个项目符号规范化为 true2。同样,下标表示 哪个 true 被引用。
第二个函数的约束是D1<T>,它将(第4个项目符号)归一化为true1。
现在,true1 确实是与 true1 相同的表达式,因此这些约束被认为是相同的。因此,D1<T> && D2<T> 包含D1<T>,而quux<int>() 是一个返回0 的明确调用。