Yes. Only概念可以被纳入。致电给foo<int>
是不明确的,因为这两个声明都“至少与另一个声明一样受到限制”。
然而,如果C1
and C2
均concept
s 而不是inline constexpr bool
s,然后声明foo()
返回0
至少会受到与声明一样的限制foo()
返回1
,并调用foo<int>
将有效并返回0
。这是更喜欢使用概念作为任意布尔常量表达式的约束的原因之一。
背景
造成这种差异的原因(概念包含,任意表达式不包含)可以最好地表达为概念的语义约束匹配 https://wg21.link/p0717r1,值得全文阅读(我不会在这里重现所有论点)。但以论文中的一个例子为例:
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
, and Z::Fooable
分别将始终选择受以下约束的候选者Z::Fooable
。这几乎肯定不是程序员想要的。
标准参考文献
归并规则在[临时构造顺序]/1.2 http://eel.is/c++draft/temp.constr.order#1.2:
原子约束A包含另一个原子约束B当且仅当A and B使用 [temp.constr.atomic] 中描述的规则是相同的。
原子约束定义在[温度构造原子] http://eel.is/c++draft/temp.constr.atomic:
An 原子约束由表达式组成E
以及来自出现在其中的模板参数的映射E
到涉及受约束实体的模板参数的模板参数,称为参数映射([temp.constr.decl])。[ Note:原子约束是通过约束规范化形成的。E
从来不符合逻辑AND
表达式也不是逻辑OR
表达。——《尾注》]
两个原子约束是完全相同的如果它们是由相同的表达根据[temp.over.link]中描述的表达式规则,参数映射的目标是等效的。
这里的关键是原子约束是formed。这就是这里的关键点。在[温度.施工.正常] http://eel.is/c++draft/temp.constr.normal:
The 正常形式 of an 表达 E
是一个约束,定义如下:
- 表达式 ( E ) 的范式是 E 的范式。
- 表达式 E1 || 的范式E2 是 E1 和 E2 范式的析取。
- 表达式 E1 && E2 的范式是 E1 和 E2 范式的合取。
- The normal form of an id-expression of the form C<A1, A2, ..., An>, where C names a concept, is the normal form of the constraint-expression of C, after substituting A1, A2, ..., An for C's respective template parameters in the parameter mappings in each atomic constraint. If any such substitution results in an invalid type or expression, the program is ill-formed; no diagnostic is required. [ ... ]
- 任何其他表达式的正常形式
E
是原子约束,其表达式为E
其参数映射是恒等映射。
For the first overload of foo
, the constraint is C1<T> && C2<T>
, so to normalize it, we get the conjunction of the normal forms of C1<T>
1 and C2<T>
1 and then we're done. Likewise, for the second overload of foo
, the constraint is C1<T>
2 which is its own normal form.
使原子约束相同的规则是它们必须由相同的元素形成表达(源级构造)。虽然这两个函数都有使用令牌序列的原子约束C1<T>
,那些不一样文字表达在源代码中。
Hence the subscripts indicating that these are, in fact, not the same atomic constraint. C1<T>
1 is not identical to C1<T>
2. The rule is not token equivalence! So the first foo
's C1<T>
does not subsume the second foo
's C1<T>
, and vice versa.
因此,暧昧。
另一方面,如果我们有:
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; }
The constraint for the first function is D1<T> && D2<T>
. The 3rd bullet gives us the conjunction of D1<T>
and D2<T>
. The 4th bullet then leads us to substitute into the concepts themselves, so the first one normalizes into true
1 and the second into true
2. Again, the subscripts indicate which true
is being referred to.
The constraint for the second function is D1<T>
, which normalizes (4th bullet) into true
1.
And now, true
1 is indeed the same expression as true
1, so these constraints are considered identical. As a result, D1<T> && D2<T>
subsumes D1<T>
, and quux<int>()
is an unambiguous call that returns 0
.