kotlin 为什么constexpr局部变量的地址不是常量表达式?

rpppsulh  于 2023-10-23  发布在  Kotlin
关注(0)|答案(4)|浏览(114)

以下constexpr函数无法编译:

constexpr void fnc()
{
    constexpr int i = 5;
    constexpr auto ptr = &i;
}

为什么ptr不能是constexpr,考虑到所有的计算都发生在一个constexpr函数中,并且它在没有声明ptr constexpr的情况下工作?

xxhby3vn

xxhby3vn1#

error message from clang非常清楚:
非静态constexpr变量“i”的地址在每次调用封闭函数时可能不同;添加“static”到给予它一个常量地址

zf2sa74q

zf2sa74q2#

iconstexpr这一事实与其地址是否为常数的问题无关。constexpr对变量的作用在[dcl.constexpr] p6中解释:
在任何constexpr变量声明中,初始化的完整表达式应该是常量表达式。
一个constexpr变量仅仅由一个常量表达式初始化。每次调用fnc都会创建一个不同的对象i,所以地址可能会改变。
一个对象需要有静态存储持续时间([expr.const] p13.3),其地址才能成为常量表达式:

static constexpr int i = 5;
constexpr auto ptr = &i; // OK
iswrvxsc

iswrvxsc3#

ptr的值是一个 core constant expression,这意味着它可以在常量求值期间求值和使用,但它不是一个 constant expression。常量表达式是具有一些附加限制的核心常量表达式。只有常量表达式可以是constexpr变量或非类型模板参数的最终值。(非类型模板参数有一些进一步的限制,我不会在这里讨论。
指针类型的常量表达式可以指向具有静态存储期限的对象,或者超过具有静态存储期限的对象末尾的对象,但它不能指向具有任何其他存储期限的对象。见[expr.const]/13。
直觉是指针类型的常量表达式必须表示一个地址,该地址的数值在链接时是已知的。(但不是在编译时,这就是为什么你不能在常量表达式中使用reinterpret_cast。)这个限制是人为的,可以删除;但是,它确实可以防止constexpr指针悬空:

constexpr void fnc()
{
    constexpr int i = 5;
    static constexpr auto ptr = &i;
}

如果上面的代码可以编译,那么第二次调用fnc时,ptr将是悬空的,因为它仍然指向第一次调用fnc时创建的i的副本。
建议P2686R2将使OP的代码有效,但上面的片段仍然无效:具有自动存储持续时间的constexpr指针/引用将被允许指向/引用在同一函数中较早声明的具有自动存储持续时间的对象,或者指向/引用具有静态存储持续时间的对象,但是具有静态存储持续时间的constexpr指针/引用将仅被允许指向/引用具有静态存储持续时间的对象。仍然不允许将具有自动存储持续时间的对象的重命名作为模板参数。

rqqzpn5f

rqqzpn5f4#

我将尝试提供一个直观的解释,因为其他人已经很好地介绍了language-lawyer方面。
iconstexpr,但它也是odr使用的,这意味着它必须在内存中给定一个地址。这就是对象身份。每个不同的示例都需要有一个不同的标识(在其生命周期内)。
考虑:

constexpr void fnc(int depth = 1, int const* ptr2 = nullptr)
{
    constexpr int i = 5;
    const auto ptr = &i;

    static_assert(ptr != ptr2);
    if (depth > 0) fnc(depth - 1, &i);
}

当您调用fnc()时,深度== 1且ptr2 == nullptr
递归调用的深度== 0,ptr2等于调用者中i的地址。
ptrptr2应该相等吗?C++说“no”,每个都是不同const int对象的地址。但这也意味着指针值不是编译时常量。

相关问题