【问题标题】:For constant expressions, why can't I use a use a pointer-type object, as standards says?对于常量表达式,为什么我不能像标准所说的那样使用指针类型的对象?
【发布时间】:2021-12-27 10:41:55
【问题描述】:

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

常量表达式要么是泛左值核心常量表达式 其值是指具有静态存储持续时间的对象或 函数或纯右值核心常量表达式,其值为 对象位置,对于该对象及其子对象:

  • ...
  • 如果对象或子对象是指针类型,则包含另一个具有静态存储期限的对象的地址,地址过去 这样一个对象的结尾(5.7),一个函数的地址,或者 空指针值

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

简单地说,我试图获取一个全局int变量global_var的地址,这是一个具有静态存储持续时间的对象(如果我没有想错的话),一切正常,正如标准所指出的那样。但是,让我感到困惑的是,当我尝试分配另一个指针类型对象(代码中的global_var_addr1)时,该对象存储了同一对象global_var 的地址,程序将无法编译。 GCC 说:

错误:“global_var_addr1”的值在常量表达式中不可用
注意:“global_var_addr1”未声明为“constexpr”

,而 Clang-Tidy 说:

错误:constexpr 变量“x2”必须由常量表达式初始化 [clang-diagnostic-error]
注意:常量表达式中不允许读取非 constexpr 变量“global_var_addr1”

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

所以我的问题是:

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
}

【问题讨论】:

    标签: c++ language-lawyer constexpr constant-expression


    【解决方案1】:

    两者都有

    constexpr const void *x2 = global_var_addr1;
    

    constexpr const void *x4 = global_var_addr2;
    

    从变量global_var_addr1/global_var_addr2 左值到右值的转换发生在它们持有的指针值之间。仅当变量的生命周期开始于常量表达式的求值期间(此处不是这种情况)或者如果它可用于常量表达式,即它是constexpr(不是此处为大小写)或由引用或const-限定整数/枚举类型的常量表达式(此处为大小写)初始化(此处不是大小写)。

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


    情况不同

    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,它不需要携带任何状态,因此左值到右值的转换不会是必要的。标准是否真的保证了这一点,我目前不确定。

    阅读当前措辞(C++20 后草案),[conv.ptr] 仅指定将空指针常量(即std::nullptr_tprvalue)转换为另一种指针类型并且 [conv.lval] 特别说明了std::nullptr_t 的左值到右值转换如何产生一个空指针常量。 [conv.lval] 还在注释中阐明了此转换不会访问内存,但我认为这不会使其不是左值到右值的转换,因为它仍然写在该标题下。

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

    constexpr const void *x5 = global_var_addr3;      // OK
    

    应该是格式错误的(无论global_var_addr3 是否为const-qualified)。

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


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

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

    【讨论】:

    • 复制该类型不需要左值到右值的转换这是哪一段说的?
    • @LanguageLawyer 是的,我实际上不确定它是否在任何地方都这么说。我重写了答案的一部分,直到找到确定的答案。
    • Clang 版本 8.0.1 及更早版本不接受 nullptr_t-case godbolt.org/z/dPGE6qM1q
    • 我们知道 [conv.lval] 特殊情况 std::nullptr_t(对类型为 cv std::nullptr_t 的对象进行左值到右值的转换会导致空指针常量),但是定义核心常量表达式的 [expr.const] 没有提到 std::nullptr_t,并且如果 constexpr const void *x5 = global_var_add3 确实进行了左值到右值的转换(我怀疑它确实如此),它不是一个常量表达式。
    • @Artyer 是的,这也是我的理解。我已经更新了答案
    【解决方案2】:

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

    const void *global_var_addr1 = &global_var;
    

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

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

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

    【讨论】:

      猜你喜欢
      • 2011-11-15
      • 2018-06-08
      • 1970-01-01
      • 2021-02-17
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 2011-01-23
      相关资源
      最近更新 更多