试图理解 C++14 中的 [basic.def.odr]/2 (N4140)

Trying to understand [basic.def.odr]/2 in C++14 (N4140)

[basic.def.odr]/2 中的示例以以下句子开头:

In the following example, the set of potential results of the initializer of n contains the first S::x subexpression, but not the second S::x subexpression.

根据本段的定义,我们如何推断 n 的初始值设定项包含第一个 S::x 子表达式,但不包含第二个 S::x 子表达式?

编辑 请参阅下面上述示例的剩余部分:

struct S { static const int x = 0; };
const int &f(const int &r);
int n = b ? (1, S::x) // S::x is not odr-used here
          : f(S::x); // S::x is odr-used here, so
                     // a definition is required

我正在使用基于 N4296 的最新 github 草稿。实际的 C++14 国际标准不包含此示例,也不包含要点编号。此处相关的规范实际上是相同的。

我们在初始化器中分解表达式:b ? (1, S::x) : f(S::x)

表达式 (1, S::x) 是类型 int const 的左值。 表达式 f(S::x) 是一个后缀表达式,一个 int const.

类型的左值

因此表达式 b ? (1, S::x) : f(S::x) 是类型 int const 的左值。因此它满足 [basic.def.odr]p2.5,潜在结果集是子表达式 (1, S::x)f(S::x).[=75= 的潜在结果集的并集]

对于第一个子表达式(1, S::x),我们通过p2.4去掉括号。结果 1, S::x 是一个逗号表达式。我们应用 p2.6 并得到 S::x。现在,p2.1 应用并告诉我们第一次出现是初始化程序的潜在结果集的一部分。

对于第二个子表达式f(S::x),仅p2.7 适用。它的潜在结果集是空的,因此它不会向初始化程序的潜在结果集添加任何内容。


至于S::x的odr-使用,[basic.def.odr]p3

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion is applied to e, or e is a discarded-value expression.

让我们把它分成几个步骤: 表达式 ex 中变量 x 的出现构成一个 odr-use,除非:

  1. 或者 ex 未被评估,
  2. 必须满足以下所有条件:
    1. "applying the lvalue-to-rvalue conversion to x yields a constant expression that does not invoke any non-trivial functions"
    2. "ex 是表达式 e" 的潜在结果集合中的一个元素,并且满足以下任一条件:
      1. "或者 左值到右值的转换应用于 e"
      2. " e是丢弃值表达式"

请注意,第 2 点表示 "is an element of the set of potential results of ANY expression e [where e fulfils certain requirements]",而不是 "all expressions e it is part of"。可以在 std-discussion mailing list.

上找到进一步的讨论

将这些步骤应用于第二次出现的 `S::x`

它是表达式 S::xf(S::x)b ? (1, S::x) : f(S::x) 的一部分。

  1. False(因为所有这些表达式都可能被求值),
  2. 必须满足以下所有条件:
    1. True(因为将 l-t-r 转换应用于 S::x 会产生一个不调用 any 函数的常量表达式)
    2. 第二次出现的 S::x 是潜在结果集的一个元素的唯一表达式是 S::x 本身。它不是 f(S::x) 的潜在结果的一部分。 必须满足以下任一条件
      1. 要么是 false(因为将 S::x 绑定到 f 的函数参数时不应用左值到右值的转换)
      2. 或 false(因为 S::x 不是丢弃值表达式)

例外不适用,S::x 在第二次出现时被 ODR 使用。

将这些步骤应用于第一次出现的 `S::x`

它是表达式 S::x1, S::x(1, S::x)b ? (1, S::x) : f(S::x) 的一部分。

  1. False(因为所有这些表达式都可能被求值),
  2. 必须满足以下所有条件:
    1. True(因为将 l-t-r 转换应用于 S::x 会产生一个不调用 any 函数的常量表达式)
    2. S::x 的第一次出现是它在初始值设定项中的所有表达式的潜在结果集中的一个元素。 必须满足以下任一条件
      1. true - 左值到右值的转换肯定不适用于表达式 S::x1, S::x(1, S::x)。可以说它适用于b ? (1, S::x) : f(S::x)(见下文)
      2. 或 false(none 个表达式是丢弃值表达式)

不清楚初始化是否应用了左值到右值的转换。可以争辩说必须读取 "value of the lvalue-expression" 才能从 int const 类型的表达式中初始化 int。如果我们遵循这个假设,那么左值到右值的转换将应用于 b ? (1, S::x) : f(S::X)S::x 的第一次出现是该表达式的潜在结果集中的一个元素(请参阅此答案的第一部分)。因此,上面的要点 3.0 适用,并且 S::xnot odr-used through the first occurrence.

您可以在问答Does initialization entail lvalue-to-rvalue conversion? Is int x = x; UB?中找到很多关于初始化中左值到右值转换的信息。这里的情况可能会更容易一些,因为 rhs 的类型为 int const。这可能需要限定转换,它需要一个纯右值操作数(这可能会隐式调用左值到右值的转换)。