6

In a response to my comment to some answer in another question somebody suggests that something like

void C::f() const
{
  const_cast<C *>( this )->m_x = 1;
}

invokes undefined behaviour since a const object is modified. Is this true? If it isn't, please quote the C++ standard (please mention which standard you quote from) which permits this.

For what it's worth, I've always used this approach to avoid making a member variable mutable if just one or two methods need to write to it (since using mutable makes it writeable to all methods).

Community
  • 1
  • 1
Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
  • What happens if a 'C' is passed by const reference to some function which invokes 'f', but the compiler chooses to pass the original 'C' by value? – Andreas Brinck Jan 04 '13 at 09:55
  • 2
    This is only undefined behaviour if the object on which it is invoked is `const`. That is why I qualified my comment with "on a const object". If this is invoked on a non-const object then it is perfectly legal and has the expected behaviour. – Mankarse Jan 04 '13 at 10:00
  • @Mankarse: I think that is the key insight, you should add that as an answer! – Frerich Raabe Jan 04 '13 at 10:03
  • @AndreasBrinck: the compiler cannot replace pass-by-const-reference with pass-by-value, at least not in general. Even if the object isn't modified, a copy of it has a different address from the original, and the callee is entitled to compare addresses and to return a pointer/reference to the by-reference parameter. When an object is passed by reference, it *must* have the same address from the POV of both the caller and the callee, and it certainly must not be replaced by an object of shorter lifetime such that the function ends up returning a dangling reference. – Steve Jessop Jan 04 '13 at 10:51
  • So, for example `const C c; bool g(const C &d) { return &c == &d; }; int main() { std::cout << g(c); }`. The compiler can't change the code such that `g` returns false. – Steve Jessop Jan 04 '13 at 11:13

3 Answers3

11

It is undefined behavior to (attempt to) modify a const object (7.1.6.1/4 in C++11).

So the important question is, what is a const object, and is m_x one? If it is, then you have UB. If it is not, then there's nothing here to indicate that it would be UB -- of course it might be UB for some other reason not indicated here (for example, a data race).

If the function f is called on a const instance of the class C, then m_x is a const object, and hence behavior is undefined (7.1.6.1/5):

const C c;
c.f(); // UB

If the function f is called on a non-const instance of the class C, then m_x is not a const object, and hence behavior is defined as far as we know:

C c;
const C *ptr = &c;
c->f(); // OK

So, if you write this function then you are at the mercy of your user not to create a const instance of C and call the function on it. Perhaps instances of C are created only by some factory, in which case you would be able to prevent that.

If you want a data member to be modifiable even if the complete object is const, then you should mark it mutable. That's what mutable is for, and it gives you defined behavior even if f is called on a const instance of C.

As of C++11, const member functions and operations on mutable data members should be thread-safe. Otherwise you violate guarantees provided by standard library, when your type is used with standard library functions and containers.

So in C++11 you would need to either make m_x an atomic type, or else synchronize the modification some other way, or as a last resort document that even though it is marked const, the function f is not thread-safe. If you don't do any of those things, then again you create an opportunity for a user to write code that they reasonably believe ought to work but that actually has UB.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 5
    +1. I would add a mention that `mutable`, as the name indicates, is there to allow mutation, while `const_cast` is there to break the type system to work with legacy, non-const correct APIs. – R. Martinho Fernandes Jan 04 '13 at 10:14
  • 1
    @BartvanIngenSchenau: heh, thanks, it's new to me so I'll probably mention it a lot for a while :-) I don't think I agree with Herb Sutter's interpretation of the text in the standard, that user-defined types are already (accidentally) required to do this. IMO it's a defect that the standard doesn't have an explicit requirement that const functions on (for example) container value types must not cause data races. But class designers "should" follow the expectations created by the way that standard classes behave, regardless of whether or not they already "must" according to the standard. – Steve Jessop Jan 04 '13 at 10:40
8

There are two rules:

  1. You cannot modify a const object.

  2. You cannot modify an object through a const pointer or reference.

You break neither rule if the underlying object is not const. There is a common misunderstanding that the presence of a const pointer or const reference to an object somehow stops that object from changing or being changed. That is simply a misunderstanding. For example:

#include <iostream>
using namespace std;

// 'const' means *you* can't change the value through that reference
// It does not mean the value cannot change

void f(const int& x, int* y)
{
    cout << "x = " << x << endl;
    *y = 5;
    cout << "x = " << x << endl;
}

int main()
{
    int x = 10;
    f(x, &x);
}

Notice no casts, nothing funny. Yet an object that a function has a const reference to is modified by that function. That is allowed. Your code is the same, it just does it by casting away constness.

However, if the underlying object is const, this is illegal. For example, this code segfaults on my machine:

#include <iostream>
using namespace std;

const int i = 5;

void cast(const int *j)
{
    *const_cast<int *>(j) = 1;
}

int main(void)
{
    cout << "i = " << i << endl;
    cast(&i);
    cout << "i = " << i << endl;
}

See section 3.4.3 (CV qualifiers) and 5.2.7 (casting away constness).

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • -1: The OP explicitly asked for a standard quote. A code example is certainly not a useful answer to this question. – Björn Pollex Jan 04 '13 at 09:33
  • I didn't downvote, but my understanding is that something is undefined behaviour if it's not explicitely defined - so just because there's no rule which forbids doing something, that doesn't automatically make it legal. – Frerich Raabe Jan 04 '13 at 09:33
  • 1
    The question is about how this case is defined in the standard. Providing a code-example that appears to work does not answer the question. – Björn Pollex Jan 04 '13 at 09:37
  • Actually, it makes sense. If the object with the `const` function was never declared `const` in the first place, modifying it through a casted `const` pointer still changes the originally non-const object. I don't see how any standard quotation can be used for this conclusion. – chris Jan 04 '13 at 09:39
  • @chris: That is correct. A const object cannot be modified. A const pointer or reference just may not be used to modify an object. His code properly casts away const, so he is no longer using a const pointer. – David Schwartz Jan 04 '13 at 09:41
  • @DavidSchwartz, Yes, I was initially getting hung up on the fact that the function being marked `const` does not make the object itself `const`. It only changes how you view the object in order to enforce its purpose. – chris Jan 04 '13 at 09:43
  • Those two rules you give are already enforced by the compiler, no - so how can you possibly break them? Maybe by using a C cast which inadvertly casts-away the constness? – Frerich Raabe Jan 04 '13 at 09:50
  • How does your sample even relate to the question? Of course you can modify an object through a `non-const` pointer to it, but that doesn't say anything about the legality of modifying it through a `const` pointer by casting away the constness. – Grizzly Jan 04 '13 at 09:52
  • So the actual answer is "Maybe, depends on whether the instance of `C` is `const` or not; `C c; c.f();` is fine, but `const C c; c.f();` is not."? – Frerich Raabe Jan 04 '13 at 09:53
  • @FrerichRaabe: See, for example, [this code](http://ideone.com/52Ismp) which breaks the rules and faults. Yes! I'll update my answer. – David Schwartz Jan 04 '13 at 09:54
  • @Grizzly: He modifies an ordinary (non-const) object through an ordinary (non-const) pointer. As I explain in the answer, the only possible reason this could be a problem is if the mere existence of a const pointer means the object cannot be modified. It doesn't mean that at all. So the question is based on a misunderstanding. There's no reason it would be illegal at all. Non-const object, non-const pointer, so no issue. – David Schwartz Jan 04 '13 at 09:57
  • 3
    Maybe I haven't read closely enough, but I don't see the question saying that the object itself is guaranteed not to be `const`, so I assume that it may very well be `const`, making the code UB. – Grizzly Jan 04 '13 at 10:05
  • @Grizzly: Right. The pointer is non-const. So if the object is const, that's a problem. Otherwise, it's just modifying a non-const object through a non-const pointer, which is obviously fine. (I updated my answer.) – David Schwartz Jan 04 '13 at 10:37
-1

Without searching any further, § 1.9/4 in the C++11 Standard reads:

Certain other operations are described in this International Standard as undefined (for example, the effect of attempting to modify a const object).

And this is what you are trying to do here. It does not matter that you are casting away constness (if you didn't do it, the behaviour is well defined: your code would fail to compile). You are attempting to modify a const object, so you are running into undefined behaviour.

Your code will appear to work in many cases. But it won't if the object you are calling it on is really const and the runtime decided to store it in read-only memory. Casting away constness is dangerous unless you are really sure that this object was not const originally.

Gorpik
  • 10,940
  • 4
  • 36
  • 56
  • 2
    -1. No, that's not what he is doing. if m_x is declared for example as int (and not const int) his use is allowed. – Johan Lundberg Jan 04 '13 at 09:57
  • It is also ok if you don't use the non-const pointer to modify the object (useful for calling functions which you know don't modify the object, but for whatever reason don't take the argument as `const`) – Grizzly Jan 04 '13 at 09:59
  • @JohanLundberg: Isn't that basically what the answer says (it will work if the object isn't originally `const`, but its not a good idea if you can't guarantee that)? – Grizzly Jan 04 '13 at 10:01
  • Downvoted since it was incorrect at the time of answering; question was modified since. – MSalters Jan 04 '13 at 10:01
  • 1
    @JohanLundberg I am basing my answer on the code in the question. `x` (I assume your `m_x` means `x`) is a data member, so it will be `const` if the object that contains it is `const`. Inside that method, you have no way of knowing whether the object on which it is called is really `const` or not. – Gorpik Jan 04 '13 at 10:03
  • Huh, well. This is a completely different question than the original. – Johan Lundberg Jan 04 '13 at 10:04
  • @JohanLundberg Ah, I see it has been changed quite a few times. I answered what I saw at the moment I wrote my answer, then. I think it is still valid for the current question. – Gorpik Jan 04 '13 at 10:07
  • @Gorpik: I'm sorry for the confusion, I augmented my code sample at one point, hoping to make it more clear - but then realized that it changes the question, so after a few moments I reverted my change. – Frerich Raabe Jan 04 '13 at 11:02
  • @FrerichRaabe Don't worry, trying to improve one's question is fine. Anyway, I think Steve Jessop's answer explains everything pretty well and didn't get as much flak as mine, so you should probably accept it. – Gorpik Jan 04 '13 at 11:08