提问人:Fedor 提问时间:7/31/2021 最后编辑:Karl KnechtelFedor 更新时间:8/1/2021 访问量:2662
为什么双重否定会改变 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
你能解释一下为什么会这样吗?
答:
这里的概念与概念相同
D
C
他们不是。约束(和概念 ID)在检查满足时被规范化,并分解为原子约束。
[临时名称]
8 concept-id 是一个简单的模板 id,其中 template-name 是 一个概念名称。concept-id 是 bool 类型的 prvalue,而不是 命名模板专用化。如果 概念的规范化约束表达式 ([temp.constr.decl]) 为 满足 ([temp.constr.constr]) 指定的模板参数 否则为 false。
在和 中有不同的看法:||
C
D
[温度.constr.normal]
2 表达式的正常形式是定义的约束 如下:
E
- 表达式的正态形式是 的正态形式。
( E )
E
- 表达式的正态形式是 E1 和 E2 的正态形式的分离。
E1 || E2
- 表达式的正态形式是 和 的正态形式的连合。
E1 && E2
E1
E2
- concept-id 的范式是 的约束表达式的范式,在将 、 、 ...、 代入 中 的相应模板参数后 每个原子约束中的参数映射。如果有任何此类替换 导致无效的类型或表达式,程序格式不正确; 无需诊断。
C<A1, A2, ..., An>
C
A1
A2
An
C
- 任何其他表达式的正常形式是原子约束,其表达式是恒等式,其参数映射是恒等式 映射。
E
E
因为原子约束是 和 。
因为只有一个原子约束是 。C
T::a
T::b
D
!!(T::a || T::b)
原子约束中的替换失败使其不满足并计算为 。 是一个满足的约束和一个不满足的约束的分离,所以它是 。 是 false,因为它唯一的原子约束具有替换失败。false
C<A>
true
D<A>
需要注意的重要一点是,根据 [temp.constr.constr],原子约束仅通过连词(通过顶层)和析取(通过顶层)组成。否定必须被视为约束的一部分,而不是约束的否定。甚至有一个非规范性说明明确指出了这一点。&&
||
考虑到这一点,我们可以研究这两种情况。 是两个原子约束的分离:和 。根据 /3,断开在检查满意度时采用短路行为。这意味着首先要检查。由于它成功了,因此无需检查第二个约束即可满足整个约束。C
T::a
T::b
T::a
C
D
另一方面,是一个原子约束: .不会以任何方式创建分离,它只是表达式的一部分。我们查看 [temp.constr.atomic]/3 来查看模板参数被替换。这意味着两者都执行了替换。该段还指出,如果替换失败,则不满足约束条件。正如前面的注释所暗示的那样,前面的否定甚至还没有被考虑在内。事实上,只有一个否定会产生相同的结果。!!(T::a || T::b)
||
T::a
T::b
现在显而易见的问题是,为什么概念是这样设计的。不幸的是,我不记得在设计师的会议演讲和其他交流中遇到过任何理由。我能找到的最好的是原始提案中的这一点:
虽然否定在我们的约束中相当普遍(参见第 5.3 节),但我们发现没有必要为运算符分配更深层次的语义。
在我看来,这可能真的低估了决定中的想法。我很想看到设计师详细说明这一点,因为我相信他要说的不仅仅是这个小引文。
评论
b
b = true