const data member initialization: simple on the surface

The well-known Rule 1 — a const data member must be initialized exactly once, no more no less.

The lesser-known Rule 2 — for class-type data member, there’s an implicit default-initialization feature that can kick in without us knowing. This default-init interacts with ctor initializer in a strange manner.

On a side note, [[safeC++]] P38 makes clever use of Rule 2 to provide primitive wrappers. If you use such a wrapper in place of a primitive field (non-const), then you eliminate the operational risk of “forgetting to initialize a non-const primitive field

The well-known Rule 3 — the proper way to explicitly initialize a const field is the ctor initializer, not inside ctor body.

The lesser-known Rule 4 — at run-time, once control passes into the ctor body, you can only modify/edit an already-initialized field. Illegal for a const field.

To understand these rules, I created an experiment in

— for primitive fields like int, Rule 2 doesn’t apply, so we must follow Rule 1 and Rule 3.

— for a class-type field like “Component”,

  • We can either leave the field “as is” and rely on the implicit Rule 2…., or
  • If we want to initialize explicitly, we must follow Rule 3. In this case, the default-init is suppressed by compiler.

In either case, there’s only one initialization per const field (Rule 1)

constexpr phrasebook

  • compile-time — value known at compile time. More strict than regular q[ const ].
  • compiler enforced — if a constexpr doesn’t have a compile-time known-value, then compiler will complain
  • subset — constexpr objects are a subset of const objects
  • — usages:
  • integral NDTTP values such as std::array length parameter. See [[effModern]]

CONST parameter of copy-ctor: j4

Background: in Apr 2020 during the covid19 lockdown, my friend Deepak get a question from MS — why copy ctor parameter is a reference. Interviewer didn’t ask for the CONST. On the CONST, here’s my letter to Deepak.

If you have a const Trade object named bigTrade, and you try to copy this Trade object, then you would need the CONST in

Trade(Trade CONST &)

I’m 99% confident that if you remove that CONST, then this copy ctor would not work with the const object bigTrade.

As to your doubts on move + temp object, your understanding of pre-c++11 language rule was probably incomplete. As a result, your understanding of c++11 is questionable.

int id=123;
Trade aClone( Trade(id) );

.. would create a temp object having id=123. This temporary can’t bind to the CONSTless copy-ctor parameter. Before or after c++11, such a copy-ctor is always a mistake. Even if there are other hacks to fix it, the only correct fix is adding the missing CONST to the copy-ctor. See temp object bind`preferences: rvr,lvr,, #SCB

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, in a const method, “this” is converted to a ptr-to-const.


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.

non-const-non-primitive field in a const method

As stated on P215 [[effSTL]], inside const methods, all fields act like const fields. This is simpler for primitive fields like an int field, but trickier for non-primitive fields … like Animal —

class C{
Animal f;
Animal const & dump() const;

Inside dump(),
– we can’t overwrite f like “f = anotherAnimal”
– we can’t call mutator methods on f like “f.grow()” — uncompilable
– we can’t remove the “const” from dump() return type — uncompilable in GCC. See also A common IV quiz.
– if Animal class overloads method age() on const, then compiler will statically bind the “f.age()” to the const version.

All these rules are compiler-enforced. No such check at run-time.

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.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.
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  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 to a mutable object, then that constness doesn’t radiate left-ward
* If RHS is any handle[2] on a const object, then yes 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) 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.

Tricky context — Constness still radiates leftward upon method-calling, because we perform a

implicit_this_param = & handleObj; //LHS is const param IFF a const method

  • constObj.constMethod() // good
  • constObj.mutableMethod() // won’t compile
  • mutableObj.constMethod() //good

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.

interpreting const var declarations c++

(based on

#1 simple rule — read   B a c k w a r d s
Q: What does ….Fred const * p1…. mean?
A: reading backward, p1 is a ….ptr to a const Fred….. Similarly,
Fred const & r1– means r1 is a …. ref to a const Fred…
Fred * const p — means p is a ….const ptr to a Fred…..
Fred const * const p — means p is a ….const ptr to a const Fred ….

Now we are ready to deal with func declarations.
#2 simple rule — identify var declarations embedded in func declarations. Essentially look at return type declarations and parameter declarations.

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

– return variable Fred const& — ref to a const Fred
– param unsigned * const — const ptr to an unsigned, just like a final Object parameter in java — you can’t reseat this ptr.

You can test most of these easily in eclipse.

const nonref variable: reasonably immutable

Reading the c++ FAQ 18.10, I got the impression that most const variables are ptr or ref.

const nonref primitive is like a java constant or java immutable.

Q: are there const nonref class object? Are they immutable?
A: Yes according to

Once a const class object has been initialized via constructor, any 
attempt to modify the member variables of the object is disallowed, as it 
would violate the constness of the object.

Just like the built-in data types (int, double, char, etc…), class objects 
can be made const by using the const keyword.

Q: can you cast away its constness?
A: very tricky.  See effC++ and the IBM arcitlce.  Short answer is no.