【问题标题】:Why can I modify const_cast-ed object in constexpr function?为什么我可以在 constexpr 函数中修改 const_cast-ed 对象?
【发布时间】:2021-09-20 04:30:13
【问题描述】:

我一直认为:

  • 写入const_cast ed 变量是UB
  • constexpr 中不允许使用 UB

所以我很困惑为什么这个代码compiles:

constexpr int fn(){
    int v = 42;
    return [v]() {
        const_cast<int&>(v)+=5;
        return v;
    }();
}
static constexpr auto val = fn();
int main() {
    return val;
}

注意:我知道没有理由不允许这样做,因为结果应该是显而易见的,我更感兴趣的是法律解释为什么允许这样做。

【问题讨论】:

  • 如果你给两个变量不同的名字return [x = v]() { ...,例子会更清楚
  • @463035818_is_not_a_number 这会破坏这个例子。
  • @Deduplicator 不在这里:godbolt.org/z/zf3fM94vd。不知道我是否理解
  • @463035818_is_not_a_number 抱歉。它只会消除该示例的一个特点。即第一个v 上没有const 很重要。我想我应该睡一会儿……
  • 某些东西有效并不罕见,因为没有理由(用你的话)“不允许它有效”。

标签: c++ undefined-behavior c++20 constexpr


【解决方案1】:

这部分是真的:

constexpr 中不允许有 UB

这部分不是:

写入const_cast-ed 变量是UB

实际规则是,来自[dcl.type.cv]/4

在 const 对象 ([basic.type.qualifier]) 的生命周期 ([basic.life ]) 导致未定义的行为。

请注意,相关部分是 object 是否为 const - 而不是您到达它的路径是否为 const。在这种情况下,v 不是 const 对象,也不是在复制时在 lambda 中创建的对象。

但是,如果您将 v 声明为 const,那么在 lambda 中声明的那个也将声明为 const,因此修改它的尝试将是未定义的行为。因此,您的代码将不再编译。

【讨论】:

  • lambdas 成员如此紧密地跟随原始版本可能会非常令人惊讶。我想知道他们为什么决定采用这种方法......
  • 既然v 是按值捕获的,难道不是因为闭包operator () 是const 限定的,而是const 限定的吗?或者它甚至不使用捕获而是使用外部范围的变量?
  • @NathanOliver 但调用operator() 的对象不是const
  • @463035818_is_not_a_number 感谢您完成这个谜题。我错过了那个对象。它仍然被声明为int
  • 啊,我的心智模型错了,我假设 mutable/(implicit) const) 指的是成员变量,但它实际上是关于 operator()。 :)
【解决方案2】:

lambda 表达式转换为类似于以下内容:

struct unnamed {
    int v;
    int operator()() const {
       const_cast<int&>(v)+=5;
       return v;
    }
};

如果没有const_cast,则无法在operator() 中修改v,因为运算符是const 方法,但v 本身不是const

同样的情况

struct foo {
    int x = 0;
    void operator() const {
        const_cast<int&>(x) += 42;
    }
};

那么这就“ok”了:

foo f;
f();

虽然这是未定义的:

const foo f;
f();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-03
    • 2011-01-26
    • 2016-11-19
    • 1970-01-01
    • 1970-01-01
    • 2013-07-15
    • 2018-09-06
    • 2023-03-25
    相关资源
    最近更新 更多