Thursday, June 23, 2011

Short little post about C++ const-correctness

Ok, so I don't really like the concept of "const-correctness" in c++, but I never really had a solid reason to dislike it besides laziness. Hence I never really bother to mark class functions as const (except in basic numerical types like Vec2D) unless the compiler whines about it.

Well, until yesterday that is. I was writing a rational number class for the hell of it (mostly as a little exercise for myself). Internally, it's represented as a numerator and denominator value. Now here's the thing: Rational(10, 4) and Rational(5, 2) are effectively identical and behave exactly the same way as far as any user could tell, even if they're represented different internally. A function, Rational::reduce(), changes the internal representation of the number without actually changing the value of the number. For all intents and purposes, this function is constant.

A few operators require reducing the rationals first, notably operator== (which most would agree should be a constant function).

Yet, reduce can't be labeled const because it changes numerator and denominator, therefore operator== either has to work with temporaries or also can't be labeled const.

- Marking numerator and denominator as mutable, but that's just stupid and unsafe since at this point you basically completely throw the notion of const correctness out the window and you may as well just not mark anything as const [which ends up happening in most of my personal projects].

- const_cast<Rational*>(this)->numerator = blahblahblah
[this is quite ugly, though it does work]

void reduce(){ //reducenumbercodehere }
void reduce() const {const_cast<Rational*>(this)->reduce();}
[this is also ugly and boilerplate-y]

What c++ SHOULD have is a way to mark a function as "mutable const". We can do it with variables already, why not also with functions? Yes it could be abused but so can mutable variables and const_cast.

I'm sure there's other examples of this problem occurring, but I figured this is a pretty easy to understand real-world example.


OpenID 36dcad70-9de5-11e0-b627-000bcdcb471e said...

It seems to me the thing you're asking for (mutable support on functions) is something that makes sense if you assume "const" is a thing there for programmer convenience. It's not though as I see things, const is a compiler optimization thing. By marking something const you're making a promise to the compiler that it can make assumptions about some particular piece of data, by then marking something mutable you're declaring an exception and saying "but you can't make the assumption for these bytes". But if *functions* get the mutable exception, then either it invalidates the const assumption for the whole object or at least invalidates forever the const assumption for the fields the mutable-function touches, at which point you might as well just mark the appropriate fields mutable and mark the function const.

I think just leaving == non-const is the correct thing to do in this case-- == really *isn't* a const operation for this type, so don't pretend it is one. Another, slightly crazier option is that you could declare 2 == functions for the type, one const, one not (I'm 90% sure C++ function overloading lets you do this?), the const== would do the reduction in temporary variables that get thrown away, the nonconst== would do the more efficient operation of reducing the numerator/denominator fields in place and leaving them that way.

- mcc

June 23, 2011 at 3:09 PM  
OpenID 36dcad70-9de5-11e0-b627-000bcdcb471e said...

...actually, having said that, I ran across this discussion and some links: which it's argued the const keyword is actually of little or no use to compilers except sometimes when objects are defined inline in code.

- mcc

June 23, 2011 at 4:38 PM  

Post a Comment

Subscribe to Post Comments [Atom]

<< Home