5

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.

5
  • example would be more clear if you give the two variables different names return [x = v]() { ... Commented Jul 9, 2021 at 22:08
  • @463035818_is_not_a_number That would break the example. Commented Jul 9, 2021 at 22:16
  • @Deduplicator not here: godbolt.org/z/zf3fM94vd. Not sure if I understand Commented Jul 9, 2021 at 22:17
  • @463035818_is_not_a_number Sorry. It would only remove one peculiarity of the example. Namely that the absence of const on the first v is important. I think I should catch some sleep... Commented Jul 9, 2021 at 22:18
  • It's not exactly unusual that something works because there is no reason (to use your words) "to not allow this to work". Commented Jul 9, 2021 at 22:34

2 Answers 2

6

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.

Sign up to request clarification or add additional context in comments.

5 Comments

The lambdas member following so closely the original can be quite surprising. I wonder why they decided on that approach...
Since v is captured by value, isn't it const qualified because the closures operator () is const qualified? Or is it not even using the capture but using the variable from the outer scope?
@NathanOliver but the object on which operator() is called is not const
@463035818_is_not_a_number Thanks for completing the puzzle. I was missing that bit of the object. it's still declared as an int.
Ah, my mental model was wrong, I assumed mutable/(implicit) const) refer to member variables, but it is actually about operator(). :)
4

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();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.