6

Code speaks more than thousand words, so...

This is undefined behaviour for mutating a const int:

struct foo {
    int x;
    void modify() { x = 3; }
};

void test_foo(const foo& f) {
    const_cast<foo&>(f).modify();
}

int main(){
    const foo f{2};
    test_foo(f);
}

What about this:

struct bar {
    void no_modify() { }
};

void test_bar(const bar& b) {
    const_cast<bar&>(b).no_modify();
}

int main(){
    const bar b;
    test_bar(b);
}

Is it allowed to call a non-const method on a const object (via const_cast) when the method does not mutate the object?

PS: I know that no_modify should have been declared as const and then the question is pointless, but assume bars definition cannot change.

PPS: Just do be sure: Dont do this at home (or anywhere else). I'd never let such code pass a review. The cast can be avoided trivially. This is a language-lawyer question.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185

2 Answers2

4

The behaviour of your code is well-defined.

The only thing that matters is if you actually modify the object. You don't. All you do is call a member function, and that function does not modify the object.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 6
    Do note this is tagged as a language lawyer question. – NathanOliver Oct 01 '19 at 15:21
  • @NathanOliver: I know - hopefully someone will give a standardese answer. I'm merely giving the correct one ;-) – Bathsheba Oct 01 '19 at 15:21
  • This might be a tough one to quote the standard for. I suspect this is legal simply because it doesn't do anything illegal and not because something in the standard explicitly allows this. So you'd probably need to justify every questionable part of the code individually with it's own standard quote. – François Andrieux Oct 01 '19 at 15:28
  • the only part in the stadnard I found so far just says that I cannot call `b.no_modify()` which is kinda natural – 463035818_is_not_an_ai Oct 01 '19 at 15:29
  • @FrançoisAndrieux yes it is hard to prove the absence of something, though I'll be happy already with a "nothing in the standard disallows it" until someone can disprove that – 463035818_is_not_an_ai Oct 01 '19 at 15:30
  • This answer doesn't strike as an agreeable one. Assume modifiable `no_modify`, which is in it's right to be, as it is not marked const. Now, if you call this through `const_cast`, the behavior gotta be undefined. It would extremely unfair to put the undefined behavior onus on the innocent `no_modify`, which played by the book. The undefined behavior has to be in the calling code, which leads us to calling non-const method. – SergeyA Oct 01 '19 at 15:45
  • 1
    @SergeyA: It's extremely relevant. The function does not modify the object. And a member function does not comprise the state of an object. – Bathsheba Oct 01 '19 at 15:48
  • 1
    Ok, may be my reasoning is wrong. It actually should be the same as with non-member function accepting non-const reference. I take my statement back. – SergeyA Oct 01 '19 at 15:56
  • @SergeyA: Absolutely! – Bathsheba Oct 01 '19 at 15:57
  • @SergeyA thats a nice analogy. I would not have asked the question for a `free_function_non_modify(bar&)` that never modifies the parameter – 463035818_is_not_an_ai Oct 01 '19 at 16:01
  • @formerlyknownas_463035818: Indeed - everyone gets excited about member functions for some reason. They are only really global functions with an implicit `this`; `const` or otherwise. I avoided mentioning this in my answer since `virtual` causes an extra headache or two. – Bathsheba Oct 01 '19 at 16:02
1

In the C++14 standard N4296 that I have access to we see a note in 5.2.11/6:

[ Note: Depending on the type of the object, a write operation through the pointer, lvalue or pointer to data member resulting from a const_cast that casts away a const-qualifier74 may produce undefined behavior (7.1.6.1). —end note ]

Technically I suspect the note may not be normative but it seems clear that the intention here is that casting away const-ness only becomes undefined behavior when a write is attempted through the pointer/reference (possibly to help support legacy code that didn't follow proper const-correctness).

Mark B
  • 95,107
  • 10
  • 109
  • 188