Why can I modify const_cast-ed object in constexpr function?

Viewed 112

I always assumed:

  • writing to const_cast ed variable is UB
  • there is no UB allowed in constexpr

So I am confused why this code 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;
}

note: I know there is no reason to not allow this to work since it is obvious what the result should be, I am more interested in the legal explanation why is this allowed.

2 Answers

This part is true:

there is no UB allowed in constexpr

This part is not:

writing to const_cast-ed variable is UB

The actual rule is, from [dcl.type.cv]/4:

Any attempt to modify ([expr.ass], [expr.post.incr], [expr.pre.incr]) a const object ([basic.type.qualifier]) during its lifetime ([basic.life]) results in undefined behavior.

Note that the relevant part is whether the object is const or not - not whether any of the path that you took to get to it is const. And in this case, v is not a const object and nor is the one created in the lambda when you're copying it.

However, if you declare v to be const, then the one declared in the lambda would also be declared const, and thus the attempt to modify it would be undefined behavior. As a result, your code will no longer compile.

The lambda expression translates to something similar to this:

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

Without the const_cast you cannot modify v inside the operator() because the operator is a const method, but v itself is not const.

Same situation as with

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

Then this is "ok":

foo f;
f();

While this is undefined:

const foo f;
f();
Related