为什么双重否定会改变 C++ 概念的价值?

Fed*_*dor 49 c++ language-lawyer c++-concepts c++20

我的一个朋友向我展示了一个带有概念的 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> );
Run Code Online (Sandbox Code Playgroud)

它被所有编译器接受:https : //gcc.godbolt.org/z/e67qKoqce

这里的概念与概念D相同C,唯一的区别是双重否定运算符!!,乍一看不会改变概念值。仍然对于 struct 来说,A这个概念C是正确的,而这个概念D是错误的。

你能解释一下为什么会这样吗?

Sto*_*ica 40

这里的概念与概念D相同C

他们不是。当检查满意度并分解为原子约束时,约束(和概念 ID)被规范化。

[临时名称]

8概念 ID 是简单模板 ID,其中模板名称是概念名称。概念 ID 是 bool 类型的纯右值,并且不命名模板特化。如果概念的规范化约束表达式 ([temp.constr.decl]) 被指定的模板参数满足 ([temp.constr.constr]),则概念 ID 评估为真,否则为假。

并且||C和 中被视为不同D

[temp.constr.normal]

2表达式的标准形式E是一个约束,其定义如下:

  • 表达式( E )的范式是 的范式E
  • 表达式E1 || E2的范式是 E1 和 E2 范式的分离。
  • 表达式E1 && E2的范式是E1和的范式的合取E2
  • 一个概念-ID的正常形态C<A1, A2, ..., An>是约束表达式的普通形式C,替换后A1A2,...,An用于C在每个原子约束参数映射的各个模板的参数。如果任何此类替换导致无效类型或表达式,则程序格式错误;不需要诊断。
  • 任何其他表达式的标准形式E是原子约束,其表达式为E且其参数映射为恒等映射。

对于C原子约束是T::aT::b
因为D只有一个原子约束是!!(T::a || T::b)

原子约束中的替换失败使其不满足并评估为falseC<A>是一个满足的约束和一个不满足的约束的分离,所以它是true. D<A>是假的,因为它的一个也是唯一的原子约束有一个替换失败。


chr*_*ris 26

要意识到的重要一点是,根据[temp.constr.constr],原子约束仅通过连接(通过 top-level &&)和分离(通过 top-level ||)组成。否定必须被视为约束的一部分,而不是约束的否定。甚至有一个非规范性说明明确指出了这一点。

考虑到这一点,我们可以检查这两种情况。C是两个原子约束的分离:T::aT::b根据 /3,在检查满意度时,析取采用短路行为。这意味着T::a首先检查。既然它成功了,整个约束C就得到了满足,而无需检查第二个。

D,在另一方面,是一个原子约束:!!(T::a || T::b)。The||不会以任何方式创建析取,它只是表达式的一部分。我们查看[temp.constr.atomic]/3以查看模板参数已被替换。这意味着T::aT::b都已执行替换。本段还指出,如果替换失败,则不满足约束。正如前面的说明所暗示的那样,甚至还没有考虑前面的否定。事实上,只有一个否定会产生相同的结果。


现在显而易见的问题是为什么要以这种方式设计概念。不幸的是,我不记得在设计师的会议演讲和其他交流中遇到过任何理由。我能找到的最好的是原始提案中的这一点:

虽然否定在我们的约束中相当普遍(参见第 5.3 节),但我们发现没有必要为运算符分配更深层次的语义。

在我看来,这可能真的低估了做出决定的想法。我很想看到设计师详细说明这一点,因为我相信他有更多的话要说,而不是这个小小的引语。