const – common subversions

Whenever I see “const”, I always remind myself at least 2 “backdoor” ways to break it

– mutable keyword

– const_cast

There are more techniques of subversion. In C we often see pointer-to-const, but in C++ ref-to-const is more common, and often known simply as a constant-reference. Suppose we have a const ref to a pointee object myOldCar. myOldCar may have another reference to it. That (non-const) reference can modify myOldCar's internal content behind our back!

Offsite — Even if myOldCar is a const object, it may have a pointer field transistorRadio. Since the actual radio object is “offsite” i.e. outside the real estate of myOldCar, that object can change its content. myOldCar is effectively non-immutable.

Incidentally, other software constructs have backdoors and subversions too

* Whenever I see singleton, I always remind myself of those subversive techniques to make multiple instances of the class.

* Whenever I see a private ctor, I always remind myself of those subversive techniques to instantiate this class without this ctor.

3 implicit conversions by c++ compiler – const

# a literal “any string” is treated as char const *. If you must assign such a literal string to a ptr-to-char variable, you must cast away the constness (Macquaries)

# If you have a const method in a non-const class instance, and if inside that method you access some instance fields, those fields are implicitly converted to const variables. See P215 [[eff STL]]

# as stated in other posts (like http://bigblog.tanbin.com/2012/02/c-const-methods-mean-this-is-bitwise.html), in a const method, “this” is converted to a ptr-to-const.

# http://bigblog.tanbin.com/2012/04/non-primitive-field-in-const-method.html

c++ CONST methods treat *this as bitwise-constant

Item 29 of [[effC++]] shows that a const method in class C1 can return a non-const pointer field of class C1. If the pointee lives at a offsite address, then whoever using the pointer can /edit/ the pointee.

The “const” on the method tells compiler to treat “this” as a pointer-to-const. More precisely, this particular method can’t modify the bitwise state of the host object. Still more precisely, it means all the bits in the real estate of the host object must remain intact during this function call.

Therefore, all fields are implicitly converted to const variables when used in this function. See also P215 [[eff STL]]

If the host object has sizeof() of 55 bytes, then that’s the size of its real estate. All the bits in the real estate must remain intact. However, the pointee lives outside the real estate and lives unprotected by const.

In java/c#, every reference field is a pointer. In contrast, java primitives and c# structs are the primary examples of “embedded” fields in a class.

reference field are writable even when host object is const

In the example below, the intVar field is a nonref and behaves as expected — can't be modified via a const method, and can't be modified via a variable marked const.

However the reference field this->intRef is not on the real estate of the host object and isn't part of the host object state, so in both cases above intRef is modifiable!

using namespace std;
template struct J{
    J(T & rhs):intRef(rhs){}
    void doIt(T a) const{
        this->intRef = a; // legal even when method is const and is called !
        //this->intVar = a; // illegal when method is const and is called
    }
    T & intRef;
    T intVar;
};
int main()
{
    int a=22;
    const J j1(a);
    j1.doIt(89);
    j1.intRef = 19; // legal even when object is const
    //j1.intVar = 19; // illegal when object is const
}

const overloading — needed in these cases

volatile and const are 2 keywords treated specially.

typedef …. my_iterator;
typedef …. my_const_interator;

my_iterator begin();
my_const_iterator begin() const;

Are we looking at method-overloading? yes due to the const. Note the return type is ignored when overloading.

The c++ const correctness article explains the motivation of const overloading. Here’s my explanation. Suppose you have 2 variables of MyType, one const var (call it varC), the other non-const var (call it var2). Suppose MyType has a non-const getter method. How would you call getter on varC ? Won’t compile[3]. You need to “const overload” the getter. So the 2 overloaded getters differ only in constness. How does compiler resolve the call? varC.getIt() would call the const getter; var2.getIt() would call the mutable getter.

[3] varC is a const “handle” on the object, so compiler promises not to modify the object state through this handle. Compiler won’t pass the unsafe (ie mutable) message getIt() to the object through this handle. Doing so creates the possibility of state mutation via a const handle.

http://www.parashift.com/c++-faq-lite/const-correctness.html#faq-18.12
gave a good coding convention, and gave detailed and simple examples.

Fred const& operator[] (unsigned index) const; ← subscript operators often come in pairs
Fred& operator[] (unsigned index); ← subscript operators often come in pairs

pbref between const levels (const radiates left

When you pass an arg variable into a function param by reference, check the const-ness of LHS vs RHS. This post is about when they differ const-wise. Note the most common and tricky situation is func pbref (ie pass-by-reference). A programmer needs to recognize them without thinking. The assignment operations below are less common but simpler.

Note — in pbref, on LHS we sometimes create a completely new ref (4-byte) with(out) const-ness. This ref has its (unknowable) new address. This address is different from address of the RHS (which must be a lval — u can’t do int& r = 4444).

I feel a const stackVar is a const object. There’s no other way to get at the object, since the const var blocks all [3] edits.

Q: Given f(const int & i), can you pass in an arg of non-const int variable?
A: yes. Const LHS is tolerant and extremely widespread[1]. The pbref process adds constness to the new LHS reference. Equivalent to the simpler but less common

int arg = 9;
const int & a = arg;

Q: Remove const — Given f(int & i), can you pass in a const int variable?
A: illegal. The arg variable is const, so it promises not to modify state. The pbref process attempts to remove the constness. Equivalent to the simpler but less common

const int arg = 9;
int & a = arg; // illegal

In summary, Const-ness radiates from RHS to LHS. Illegal to block it.
* If RHS is a const ptr on the object, then that constness doesn’t radiate left-ward
* If RHS is a handle[2] on a const object, then that constness radiates left-ward.
* If RHS is a handle on a mutable object, then LHS can be anything.

sound byte– if you have only a handle on a const object, then you can’t [3] modify its state even if you copy that handle. Object is not editable *via* this handle.

sound byte — If a method or a variable is declared without “const”, compiler assumes this “handle” (can and therefore) will modify object state.

Compiler disallows assigning a const RHS handle to a mutable LHS handle. “Handle” is typically a ref. (For a ptr, “const handle” means ptr-to-const). However, if you deep-copy a target object (not the 4-byte address), then const-ness doesn’t radiate left. You can make a mutable deep copy from a const object, without compromising const-ness of the RHS object. All subsequent “edits” happen on the deep copy.

Different context — Constness radiates rightward upon method-calling.
constObj.constMethod() // good
constObj.mutableMethod() // won’t compile

Q: need to explicitly remove constness of a q(const char *), which is a c-string
A: Yes. Constness radiates left. Use const_cast. See post on const char *

[1] copiers and assignments usually have const ref params.
[2] handle can mean a nonref variable or a ref/ptr
[3] except via explicit const_cast

(below is a piece of work in progress….)
Now we can better understand why auto_ptr is bad for containers. Given an auto_ptr object A. A’s copier and assignment both take a non-const RHS. Container functions need copier with const param ie a const LHS, but
* can they enforce that copier LHS be declared const? No. If you could then auto_ptr + container would have been illegal combination??
* can they enforce that copier RHS be declared const? No compiler can’t enforce it — const radiates left. Compiler can only enforce LHS to be const.