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

fquxozlt  于 2023-02-17  发布在  其他
关注(0)|答案(2)|浏览(162)

这是C20标准(ISO/IEC 14882:2020)第13.5.4节(temp.constr.normal(https://eel.is/cdraft/temp.constr.normal))第1段的示例(着重号为我):
概念id C〈A1,A2,...,An〉的范式是在用A1,A2,...,An替换每个原子约束中的参数Map中C的相应模板参数之后,C的 constraint-expression 的范式。如果任何这样的替换导致无效类型或表达式,则程序是病态的;不需要诊断。

template<typename T> concept A = T::value || true;
template<typename U> concept B = A<U*>;
template<typename V> concept C = B<V&>;

B的 constraint-expression 的规范化有效,结果为T::value(使用MapT -> U*)V true(具有空Map),尽管表达式T::value对于指针类型T病态形式的。C的 constraint-expression 的规范化导致程序是病态形式的,因为它将在参数Map中形成无效类型V&*
我知道C语言会使程序格式错误(以及为什么)。然而,我不清楚B是否会导致程序是病态的。文本声明B的规范化是有效的,但同时声明表达式T::value由于该指针类型而为病态形式这是否意味着只有过程的规范化部分是有效的,而程序本身在稍后阶段检查T::value时是病态的?或者程序在任何情况下都有效,并且以某种方式跳过/避免了对T::value的检查?
我检查了Godbolt的Compiler Explorer,GCC和Clang看起来都没问题,然而,由于标准说“* 不需要诊断 *",这没有多大帮助。

ej83mcc0

ej83mcc01#

概念B是有效的,因为你可以传递一个指针给概念A。在A本身内部,指针不能访问::value,但是根据规范[temp.constr.atomic],这不会被认为是一个错误,而是false,那么概念A上的|| true将使整个表达式true
注意,如果我们将int&传递给概念B,那么我们的代码将是IFNDR,因为B将试图传递给A一个无效类型(int&*)。
概念C是IFNDR,因为它传递了一个对B的引用,B试图将指向该引用的指针传递给A,并且V&*类型也是无效的。
尝试使用static_assert计算 * 格式错误的不需要诊断的 * 表达式不一定有助于回答表达式是否有效的问题,例如the compiler is not required to fail a static_assert on an ill-formed-no-diagnostic-required expression

s5a0g9ez

s5a0g9ez2#

注意,每个规范化约束由2部分组成:
原子约束和关联的参数Map。
让我们将三个示例概念的每个约束分为两部分:
在您的示例中,概念A的规范化形式将是这两个约束的析取:

  • 原子表达式:X::value

参数Map:X ↦ T

  • 原子表达式:true

无参数Map
概念B的规范化形式将是这两个约束的析取:

  • 原子表达式:X::value

参数Map:X ↦ U*

  • 原子表达式:true

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

  • 原子表达式:X::value

参数Map:X ↦ V&*

  • 原子表达式:x1米11米1x

无参数Map

如何形成参数Map

形成原子表达式的参数Map非常简单:
默认情况下,原子表达式总是以标识参数Map开始(即没有类型修改):
13.5.4约束标准化临时约束标准
(1)表达式E的标准形式是如下定义的约束:
[...]
(1.5)任何其他表达式E的范式是原子约束,其表达式是E**,其参数Map是恒等Map。
而获得非恒等参数Map的唯一方法是在约束内命名另一个概念:
13.5.4约束标准化临时约束标准
(1.4)concept-id C<A1, A2, ..., An>的范式是在用A1,A2,...,An替换每个原子约束中的参数Map中C的相应模板参数之后C,的约束表达式的范式。[...]
以下是一些例子:

template<class T> constexpr bool always_true = true;

// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ T (identity)
template<class T> concept Base = always_true<T>;

// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ U (identity) 
template<class U> concept Foo = Base<U>;

// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ V::type
template<class V> concept Bar = Base<typename V::type>;

// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ W&&
template<class W> concept Baz = Base<W&&>;

您引用的部分

这就把我们带回到你最初引用的部分:
13.5.4约束标准化临时约束标准
(1.4)概念id C<A1, A2, ..., An>的范式是在用A1、A2、...、An替换每个原子约束中的参数Map中C的相应模板参数之后C的约束表达式的范式。如果任何这样的替换导致无效类型或表达式,则程序是病态的;不需要诊断。
注意,突出显示的语句只适用于参数Map,而不适用于原子表达式本身。
这就是为什么示例中的概念C是病态的NDR--因为其原子表达式的参数Map形成了一个无效类型(指向引用的指针):X ↦ V&*
注意,在规范化阶段,替换V的实际类型并不重要;唯一重要的是Map本身是否形成了无效的类型或表达式。
下面是几个例子:

template<class T> constexpr bool always_true = true;

// well-formed
// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ T (identity)
template<class T> concept Base = always_true<T>;

// well-formed
// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ U::type
template<class U> concept Foo = Base<typename U::type>;

// ill-formed, ndr (invalid parameter mapping)
// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ V*::type
template<class V> concept Bar = Foo<V*>;

// ill-formed, ndr (invalid parameter mapping)
// Atomic constraint: always_true<X>
// Parameter mapping: X ↦ W&*
template<class W> concept Baz = Foo<W&>;

编译期间事件的大致时间线

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

  • 当关联声明的约束被确定或者概念的值被评估时,约束规范化将发生。

这由下式给出:
13.5.4约束标准化临时约束标准
[Note 1]当确定声明的关联约束时以及当评估命名概念专门化的id表达式的值时,执行约束表达式的规范化。
如果参数Map形成了无效的类型或表达式,那么这就是你的程序将变成格式错误的地方。

  • 约束规范化后,实际类型将替换为约束:

13.5.2.3 Atomic constraints temp.constr.atomic
(3)若要确定是否满足原子约束,首先将参数Map和模板参数替换到其表达式中。如果替换导致无效的类型或表达式,则不满足约束。
请注意,此时允许形成无效类型或表达式-如果是这种情况,则约束的结果将简单地为false

结论

为了回答你们的问题:

  • 这是否意味着只有过程的规范化部分是有效的,但程序本身在稍后检查T::value时是病态的?

概念AB是合式的。
概念C在规范化过程中是病态的。
实际原子约束T::value在这种情况下并不重要;它也可以简单地为always_true<T>

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

只要概念C从未被规范化,该程序就有效。
也就是说,显式地计算它或将它用作约束将使程序格式不正确。
示例:

// evaluates concept C
//   -> results in normalization of C
//   -> ill-formed, ndr
static_assert(C</* something */>); 

template<C T>
void foo() {}

// constraints of foo will be determined
//   -> results in normalization of C
//   -> ill-formed, ndr
foo</* something */>();

相关问题