对于常量表达式,为什么我不能像标准所说的那样使用指针类型的对象?

For constant expressions, why can't I use a use a pointer-type object, as standards says?

我试图找出 cpp11/14 中 constexpr 的限制。我在CPP14-5.19-4中找到了一些使用要求:

A constant expression is either a glvalue core constant expression whose value refers to an object with static storage duration or to a function, or a prvalue core constant expression whose value is an object where, for that object and its subobjects:

  • ...
  • if the object or subobject is of pointer type, it contains the address of another object with static storage duration, the address past the end of such an object (5.7), the address of a function, or a null pointer value.

我已经 运行 对涉及地址运算符 & 的表达式进行了一些测试(代码如下所示),以确保上面引用的标准语句的正确性。

简单的说,我尝试取一个全局int变量global_var的地址,这是一个静态存储期[=的对象45=](如果我没有想错的话),一切都按照标准指出的那样工作。但是,让我感到困惑的是,当我试图分配另一个指针类型对象(代码中的global_var_addr1)时,它存储了同一对象的地址global_var,程序将无法编译。 GCC 说:

error: the value of ‘global_var_addr1’ is not usable in a constant expression
note: ‘global_var_addr1’ was not declared ‘constexpr’

,而 Clang-Tidy 说:

error: constexpr variable 'x2' must be initialized by a constant expression [clang-diagnostic-error]
note: read of non-constexpr variable 'global_var_addr1' is not allowed in a constant expression

我不知道为什么,有什么我错过的吗?

所以我的问题是:

1.为什么,在常量表达式中,我不能使用指针类型对象,它包含具有静态存储持续时间的对象的地址,正如标准所说?
2。当指定对象 auto 时,为什么在与 (1) 相同的上下文中一切都不同?

欢迎任何建议,提前致谢!


代码:

const int global_var_c = 123;
int global_var = 123;
const void *global_var_addr1 = &global_var;
const void *global_var_addr2 = nullptr;
auto global_var_addr3 = nullptr;

auto  main() -> int
{
    constexpr const int x00 = global_var_c;           // OK
    constexpr const void *x0 = &global_var;           // OK

    // Operate on the object of pointer type
    constexpr const void *x1 = &global_var_addr1;      // OK
    constexpr const void *x2 = global_var_addr1;       // ERROR: read of non-constexpr variable 'global_var_addr1'...

    // Operate on nullptr
    constexpr const void *x3 = &global_var_addr2;     // OK
    constexpr const void *x4 = global_var_addr2;      // ERROR: read of non-constexpr variable 'global_var_addr2'...

    // Operate on nullptr (with type deduction)
    constexpr const void *x5 = global_var_addr3;      // OK
    constexpr const void *x6 = &global_var_addr3;     // OK
}

这里声明的变量显然不是constexpr(甚至const):

const void *global_var_addr1 = &global_var;

并且您不能使用非 constexpr 值来初始化 constexpr 值。所以编译失败也就不足为奇了:

constexpr const void *x2 = global_var_addr1; // ERROR: read of non-constexpr

非 constexpr 值的地址可以用于您所展示的情况,但是,存储在变量中的值和变量的地址不是一回事。

两者都

constexpr const void *x2 = global_var_addr1;

constexpr const void *x4 = global_var_addr2;

a lvalue-to-rvalue 转换发生在变量 global_var_addr1/global_var_addr2 glvalue 到它们持有的指针值之间。仅当变量的生命周期在常量表达式求值期间开始时(此处不是这种情况)或者如果它可用于常量表达式,这意味着它是[=17],这样的转换才被允许=](此处不是这种情况)或由常量表达式初始化(​​此处是这种情况)引用或const限定的integral/enumeration类型(不是这里的案例)。

因此初始化器不是常量表达式。


这与

的情况不同
constexpr const int x00 = global_var_c;

因为 global_var_cconst 合格的整数类型。


我不太确定

constexpr const void *x5 = global_var_addr3;      // OK

直觉上它应该有效,因为 nullptr 的类型以及因此 global_var_addr3 的推导类型是 std::nullptr_t,它不需要携带任何状态,因此 lvalue-to-rvalue 不需要转换。标准是否真的保证了这一点,我现在不确定。

阅读当前的写法(post-C++20草案),[conv.ptr]指定仅转换空指针常量(即prvalue std::nullptr_t) 到另一种指针类型,[conv.lval] 具体说明了 std::nullptr_t 的 lvalue-to-rvalue 转换如何产生空指针常量。 [conv.lval] 还在注释中澄清了此转换不访问内存,但我认为这不会使它不是 lvalue-to-rvalue 转换,因为它仍然写在该标题下。

所以在我看来,严格阅读标准

constexpr const void *x5 = global_var_addr3;      // OK

应该是ill-formed(无论global_var_addr3是否符合const)。

Here 是一个开放的 clang 错误。标准委员会似乎有一个 link 内部讨论,我无法访问。


在任何情况下,auto 占位符都无关紧要。您可以直接为它编写 std::nullptr_t

所有这些都是成为核心常量表达式的要求,这是您在问题中提到的要求的先决条件。