C++20 概念:约束规范化

J L*_*J L 16 c++ templates constraints c++-concepts c++20

这是 C++20 标准 (ISO/IEC 14882:2020) 第 13.5.4 节 ( [temp.constr.normal] ) 第 1 段中的示例(重点是我的):

\n
\n

概念 ID C<A1 , A2 , ..., An> 的范式是用A1 , A2 , ..., An 替换 C\xe2\x80\ 后 C 的约束表达式的范式每个原子约束中的参数映射中的 x99 各自的模板参数。如果任何此类替换导致无效类型或表达式,则该程序格式错误;无需诊断。

\n
\n
template<typename T> concept A = T::value || true;\ntemplate<typename U> concept B = A<U*>;\ntemplate<typename V> concept C = B<V&>;\n
Run Code Online (Sandbox Code Playgroud)\n
\n

B\xe2\x80\x99s 约束表达式的规范化是有效的,并且会导致T::value(具有映射T -> U*) V true(具有空映射),尽管该表达式T::value对于指针类型来说格式不正确T。C\xe2\x80\x99s 约束表达式的规范化导致程序格式错误,因为它会V&*在参数映射中形成无效类型。

\n
\n

我知道 C 会使程序格式错误(以及原因)。然而,我不清楚 B 是否会导致程序格式错误。文本指出 B\ 的规范化是有效的,但同时它指出T::value由于该指针类型(我理解),该表达式的格式不正确。这是否意味着只有过程的规范化部分有效,但程序本身在后期检查时格式不正确T::valueT::value或者该程序在任何情况下都有效并且以某种方式跳过/避免了检查?

\n

我检查了 Godbolt 的编译器资源管理器,GCC 和 Clang 似乎对此都很好。然而,由于标准说“不需要诊断”,这并没有多大帮助。

\n

Ami*_*rsh 16

概念 B 是有效的,因为您可以将指针传递给概念 A。在 A 本身内部,指针无法访问::value,但根据规范[temp.constr.atomic],这不会被视为错误,而是被视为false,那么|| trueon 概念 A 将构成整个表达式true

请注意,如果我们将 int& 传递给概念 B,那么我们的代码将是 IFNDR,因为 B 会尝试将无效类型传递给 A ( int&*)。

概念 C 本身就是 IFNDR,因为它传递了对 B 的引用,而 B 试图将指向该引用的指针传递给 A,并且类型再次V&*无效。


尝试使用评估格式错误的无诊断表达式static_assert不一定有助于回答表达式是否有效的问题,因为编译器不需要在格式错误的无诊断表达式static_assert上失败。所需表达


Tur*_*ght 13

请注意,每个规范化约束由两部分组成:
\n原子约束和关联的参数映射。

\n
\n

让我们将三个示例概念的每个约束分为这两部分:

\n

在您的示例中,概念的规范化形式A将是这两个约束的析取:

\n
    \n
  • 原子表达式:X::value
    \n参数映射:X \xe2\x86\xa6 T
  • \n
  • 原子表达式: true
    \n无参数映射
  • \n
\n

概念的规范化形式B是这两个约束的析取:

\n
    \n
  • 原子表达式:X::value
    \n参数映射:X \xe2\x86\xa6 U*
  • \n
  • 原子表达式: true
    \n无参数映射
  • \n
\n

概念的规范化形式C将是这两个约束的析取:

\n
    \n
  • 原子表达式:X::value
    \n参数映射:X \xe2\x86\xa6 V&*
  • \n
  • 原子表达式: true
    \n无参数映射
  • \n
\n
\n

参数映射是如何形成的

\n

形成原子表达式的参数映射很简单:

\n

默认情况下,原子表达式总是以标识参数映射开始(即没有类型修改):

\n
\n

13.5.4 约束规范化 [[temp.constr.normal]]
\n (1)表达式 E 的范式是一个约束,其定义如下:
\n[...]
\n (1.5)范式任何其他表达式 E 的原子约束,其表达式为 E ,其参数映射为恒等映射。

\n
\n

获得非恒等参数映射的唯一方法是在约束内命名另一个概念:

\n
\n

13.5.4 约束规范化 [[temp.constr.normal]]
\n (1.4)概念 ID 的范式C<A1, A2, ..., An>是 C 的约束表达式的范式,替换为 A1, A2, ..., An对于每个原子约束中的参数映射中的 C 各自的模板参数。[...]

\n
\n

这里有一些例子:

\n
template<class T> constexpr bool always_true = true;\n\n// Atomic constraint: always_true<X>\n// Parameter mapping: X \xe2\x86\xa6 T (identity)\ntemplate<class T> concept Base = always_true<T>;\n\n// Atomic constraint: always_true<X>\n// Parameter mapping: X \xe2\x86\xa6 U (identity) \ntemplate<class U> concept Foo = Base<U>;\n\n// Atomic constraint: always_true<X>\n// Parameter mapping: X \xe2\x86\xa6 V::type\ntemplate<class V> concept Bar = Base<typename V::type>;\n\n// Atomic constraint: always_true<X>\n// Parameter mapping: X \xe2\x86\xa6 W&&\ntemplate<class W> concept Baz = Base<W&&>;\n
Run Code Online (Sandbox Code Playgroud)\n
\n

您引用的部分

\n

这让我们回到了您最初引用的部分:

\n
\n

13.5.4 约束规范化 [[temp.constr.normal]]
\n (1.4)概念 ID 的范式C<A1, A2, ..., An>是 C 的约束表达式的范式,替换为 A1, A2, ..., An对于每个原子约束中的参数映射中的 C 各自的模板参数。如果任何此类替换导致无效类型或表达式,则该程序格式错误;无需诊断。

\n
\n

请注意,突出显示的语句仅适用于参数映射,不适用于原子表达式本身。

\n

这就是为什么C您的示例中的概念格式不正确,NDR - 因为其原子表达式的参数映射形成无效类型(指向引用的指针):X \xe2\x86\xa6 V&*

\n

请注意,在规范化阶段替换的实际类型V并不重要;唯一重要的是映射本身是否形成无效的类型或表达式。

\n

这里还有几个例子:

\n
template<class T> constexpr bool always_true = true;\n\n// well-formed\n// Atomic constraint: always_true<X>\n// Parameter mapping: X \xe2\x86\xa6 T (identity)\ntemplate<class T> concept Base = always_true<T>;\n\n// well-formed\n// Atomic constraint: always_true<X>\n// Parameter mapping: X \xe2\x86\xa6 U::type\ntemplate<class U> concept Foo = Base<typename U::type>;\n\n// ill-formed, ndr (invalid parameter mapping)\n// Atomic constraint: always_true<X>\n// Parameter mapping: X \xe2\x86\xa6 V*::type\ntemplate<class V> concept Bar = Foo<V*>;\n\n// ill-formed, ndr (invalid parameter mapping)\n// Atomic constraint: always_true<X>\n// Parameter mapping: X \xe2\x86\xa6 W&*\ntemplate<class W> concept Baz = Foo<W&>;\n
Run Code Online (Sandbox Code Playgroud)\n
\n

编译期间事件的粗略时间表

\n

为了回答程序何时出现格式错误的 ndr 问题,我们需要确定编译期间事件发生的顺序。

\n
    \n
  • 当确定关联声明的约束或评估概念的值时,将发生约束规范化。
    \n这是由下式给出的:

    \n
    \n

    13.5.4 约束规范化 [[temp.constr.normal]]
    \n [注 1]在确定声明的关联约束以及评估命名概念特化的 id 表达式的值时,执行约束表达式的规范化。

    \n
    \n

    如果参数映射形成无效的类型或表达式,这就是您的程序将变得格式错误的地方。

    \n
  • \n
  • 约束标准化后,实际类型将被替换为约束:

    \n
    \n

    13.5.2.3 原子约束 [[temp.constr.atomic]]
    \n (3)为了确定是否满足原子约束,首先将参数映射和模板实参代入其表达式中。如果替换导致无效类型或表达式,则不满足约束。

    \n
    \n

    请注意,此时允许形成无效类型或表达式 - 如果是这种情况,则约束的结果将只是false

    \n
  • \n
\n
\n

结论

\n

所以回答你的问题:

\n
    \n
  • \n
    \n

    这是否意味着只有过程的规范化部分有效,但程序本身在后期检查时格式不正确T::value

    \n
    \n

    概念AB结构良好。
    \n概念格式C不正确,ndr 在规范化过程中。
    \n在这种情况下,实际的原子约束T::value并不重要;也可以简单地是always_true<T>

    \n
  • \n
  • \n
    \n

    或者程序在任何情况下都有效并且 T::value 的检查以某种方式被跳过/避免?

    \n
    \n

    只要概念C从未标准化,该计划就是有效的。
    \ni.e. 明确地评估它或使用它作为约束会使您的程序格式错误,ndr。

    \n

    例子:

    \n
    // evaluates concept C\n//   -> results in normalization of C\n//   -> ill-formed, ndr\nstatic_assert(C</* something */>); \n\ntemplate<C T>\nvoid foo() {}\n\n// constraints of foo will be determined\n//   -> results in normalization of C\n//   -> ill-formed, ndr\nfoo</* something */>();  \n
    Run Code Online (Sandbox Code Playgroud)\n
  • \n
\n

  • 该映射仅考虑约束内出现的模板参数,因此“true”的映射为空。 (2认同)