为什么双重否定会改变C++概念的价值?
Why does double negation change the value of C++ concept?
我的一个朋友向我展示了一个C++20的程序,有概念,让我很困惑:
struct A { static constexpr bool a = true; };
template <typename T>
concept C = T::a || T::b;
template <typename T>
concept D = !!(T::a || T::b);
static_assert( C<A> );
static_assert( !D<A> );
它被所有编译器接受:https://gcc.godbolt.org/z/e67qKoqce
这里的概念D
和概念C
是一样的,唯一不同的是双重否定运算符!!
,乍一看应该不会改变概念值.仍然对于结构 A
,概念 C
为真,概念 D
为假。
你能解释一下为什么会这样吗?
Here the concept D
is the same as the concept C
他们不是。在检查满意度并分解为原子约束时,约束(和概念 ID)被规范化。
[temp.names]
8 A concept-id is a simple-template-id where the template-name is
a concept-name. A concept-id is a prvalue of type bool, and does not
name a template specialization. A concept-id evaluates to true if the
concept's normalized constraint-expression ([temp.constr.decl]) is
satisfied ([temp.constr.constr]) by the specified template arguments
and false otherwise.
而 ||
在 C
和 D
中有不同的看法:
[temp.constr.normal]
2 The normal form of an expression E
is a constraint that is defined
as follows:
- The normal form of an expression
( E )
is the normal form of E
.
- The normal form of an expression
E1 || E2
is the disjunction of the normal forms of E1 and E2.
- The normal form of an expression
E1 && E2
is the conjunction of the normal forms of E1
and E2
.
- The normal form of a concept-id
C<A1, A2, ..., An>
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.
- The normal form of any other expression
E
is the atomic constraint whose expression is E
and whose parameter mapping is the identity
mapping.
对于C
,原子约束是T::a
和T::b
。
对于 D
只有一个原子约束,即 !!(T::a || T::b)
.
原子约束中的替换失败使其不满足并评估为 false
。 C<A>
是一个满足的约束和一个不满足的约束的析取,所以它是 true
。 D<A>
是错误的,因为它的唯一原子约束具有替换失败。
要认识到的重要一点是 [temp.constr.constr], atomic constraints are composed only via conjunctions (through top-level &&
) and disjunctions (through top-level ||
). Negation must be thought of as part of a constraint, not the negation of a constraint. There's even a non-normative note 明确指出了这一点。
考虑到这一点,我们可以检查这两个案例。 C
是两个原子约束的析取:T::a
和 T::b
。 Per /3,析取在检查满意度时采用 short-circuiting 行为。这意味着 T::a
首先被检查。由于它成功了,所以整个约束 C
都得到了满足,而无需检查第二个约束。
另一方面,D
是一个原子约束:!!(T::a || T::b)
。 ||
不会以任何方式创建析取,它只是表达式的一部分。我们查看 [temp.constr.atomic]/3 以查看模板参数被替换。这意味着 T::a
和 T::b
都执行了替换。本段还指出,如果替换失败,则不满足约束条件。正如前面的说明所暗示的那样,甚至还没有考虑前面的否定。事实上,只有一个否定会产生相同的结果。
现在显而易见的问题是为什么要这样设计概念。不幸的是,我不记得在设计师的会议演讲和其他交流中遇到过任何理由。我能找到的最好的是原始提案中的这一点:
While negation has turned out to be fairly common in our constraints (see Section 5.3), we have not found it necessary to assign deeper semantics to the operator.
在我看来,这可能真的低估了决策中的想法。我很乐意看到设计师详细说明这一点,因为我相信他比这个小引述有更多的话要说。
我的一个朋友向我展示了一个C++20的程序,有概念,让我很困惑:
struct A { static constexpr bool a = true; };
template <typename T>
concept C = T::a || T::b;
template <typename T>
concept D = !!(T::a || T::b);
static_assert( C<A> );
static_assert( !D<A> );
它被所有编译器接受:https://gcc.godbolt.org/z/e67qKoqce
这里的概念D
和概念C
是一样的,唯一不同的是双重否定运算符!!
,乍一看应该不会改变概念值.仍然对于结构 A
,概念 C
为真,概念 D
为假。
你能解释一下为什么会这样吗?
Here the concept
D
is the same as the conceptC
他们不是。在检查满意度并分解为原子约束时,约束(和概念 ID)被规范化。
[temp.names]
8 A concept-id is a simple-template-id where the template-name is a concept-name. A concept-id is a prvalue of type bool, and does not name a template specialization. A concept-id evaluates to true if the concept's normalized constraint-expression ([temp.constr.decl]) is satisfied ([temp.constr.constr]) by the specified template arguments and false otherwise.
而 ||
在 C
和 D
中有不同的看法:
[temp.constr.normal]
2 The normal form of an expression
E
is a constraint that is defined as follows:
- The normal form of an expression
( E )
is the normal form ofE
.- The normal form of an expression
E1 || E2
is the disjunction of the normal forms of E1 and E2.- The normal form of an expression
E1 && E2
is the conjunction of the normal forms ofE1
andE2
.- The normal form of a concept-id
C<A1, A2, ..., An>
is the normal form of the constraint-expression ofC
, after substitutingA1
,A2
, ...,An
forC
'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.- The normal form of any other expression
E
is the atomic constraint whose expression isE
and whose parameter mapping is the identity mapping.
对于C
,原子约束是T::a
和T::b
。
对于 D
只有一个原子约束,即 !!(T::a || T::b)
.
原子约束中的替换失败使其不满足并评估为 false
。 C<A>
是一个满足的约束和一个不满足的约束的析取,所以它是 true
。 D<A>
是错误的,因为它的唯一原子约束具有替换失败。
要认识到的重要一点是 [temp.constr.constr], atomic constraints are composed only via conjunctions (through top-level &&
) and disjunctions (through top-level ||
). Negation must be thought of as part of a constraint, not the negation of a constraint. There's even a non-normative note 明确指出了这一点。
考虑到这一点,我们可以检查这两个案例。 C
是两个原子约束的析取:T::a
和 T::b
。 Per /3,析取在检查满意度时采用 short-circuiting 行为。这意味着 T::a
首先被检查。由于它成功了,所以整个约束 C
都得到了满足,而无需检查第二个约束。
D
是一个原子约束:!!(T::a || T::b)
。 ||
不会以任何方式创建析取,它只是表达式的一部分。我们查看 [temp.constr.atomic]/3 以查看模板参数被替换。这意味着 T::a
和 T::b
都执行了替换。本段还指出,如果替换失败,则不满足约束条件。正如前面的说明所暗示的那样,甚至还没有考虑前面的否定。事实上,只有一个否定会产生相同的结果。
现在显而易见的问题是为什么要这样设计概念。不幸的是,我不记得在设计师的会议演讲和其他交流中遇到过任何理由。我能找到的最好的是原始提案中的这一点:
While negation has turned out to be fairly common in our constraints (see Section 5.3), we have not found it necessary to assign deeper semantics to the operator.
在我看来,这可能真的低估了决策中的想法。我很乐意看到设计师详细说明这一点,因为我相信他比这个小引述有更多的话要说。