Mastering ‘const’ in C++

‘const’ keyword is a type qualifier that can be associated with an object. It tells the compiler to mark the object as read-only.

Trying to directly modify such an object will result in a compiler error. Trying to indirectly modify it could result in undefined behavior. A const object is generally stored in the ‘data’ section of the program’s memory. But the location of its storage is more or less compiler dependent.

Most compilers may optimize out a const variable and just hard code its value to the compiled code. As a result, a const variable may not even occupy any space in memory.

There are several uses of this keyword.


1) A variable can be declared as ‘const’. A const variable has to be
initialized at the point of its declaration. The lifetime of such a variable is same as the lifetime of the program. But its scope is limited based on where it is declared.

const int a;    //ERROR. A const variable
                //must be initialized.
const int a=10; //OK
int const b = 20; //OK

char a = '1';
const char b= a;  //OK
const char &c = a; //OK.
char &d = b; //ERROR. constness of an object
             //cannot be reduced.

const int e = 4;
int f = e; //OK

{
    const float f = 1.4;
}
// At this point, 'f' still exists
// in memory but out of scope.


2) A function parameter can be marked as ‘const’. This ensures that the argument passed to the function does not get modified within the function.

void func1(int& a){
}

void func2(const int& a){
}

int main()
{
   int a = 6;
   func2(a); //OK

   const int b = 5;
   func1(b); //ERROR. Decreasing constness is not
             //allowed.

   func1(5); //ERROR. An l-value reference(int&) cannot
             //be initialized with an r-value (5).

   func2(5) //OK. A const l-value reference(const int&)
            // can be initialized with an r-value(5).
}

3) Class member functions can be marked as ‘const’ if they do not modify their member variables. A const qualified instance of the class can only invoke the const methods.

class Test{
    int a;
    public:

    void func1() const {
        a += 5; //ERROR
    }

    void setA(){
        a = 5; //OK
    }

    int getA() const {
        return a; //OK
    }
};

int main(){
    Test t1;
    t1.setA(4);//OK
    t1.getA(); //OK

    const Test t2;
    t2.setA(4); // ERROR
}

4) It is possible to alter the constness of a variable using ‘const_cast’. const_cast allows us to remove the ‘const’ and ‘volatile’ properties of types. It works with pointers and references. For example,


5) A function return value can also be marked as const. But it would not make sense all the time. For example, if the returned type is passed by value, it is generally redundant to qualify the return type with const.

// In the below method, a pointer to a
// constant integer is returned.
// Therefore, the caller of this method
// will not be able to modify the
// underlying object. This is a valid
// use case.
const int* func1(){
    int* a = new int(5);
    return a;
}

// In the below method, a reference to a
// constant integer is returned.
// Therefore, the caller of this method
// will not be able to modify the
// underlying object. This is also a
// valid use case.
const int& func2(){
    int* a = new int(5);
    return *a;
}

// In the below method, the return type
// is a const pointer to an integer.
// But this pointer itself is passed by value
// and therefore, the caller of this method
// will still be able to modify the underlying
// object. The use of const is redundant here.
int * const func3(){
    int* a= new int(5);
    return a;
}

// Similarly,the return type of the below method
// is a const integer but return value
// is passed by value. So there is no real
// benefit for qualifying the return type
// with const.
const int func4(){
    return 5;
}

int main(){
    int b= func4();
    b+= 2;
}

As shown in the previous example, returning a const qualified basic type is more or less useless when passed by value. But when we do the same with user defined types, things could be a little different. Returning a const user defined type could prevent the caller from invoking non const member methods on the returned object as shown in the example below. Please note that it is still possible to assign the returned value to a non-const variable and use it freely.

const std::string get(){}

int main(){
    get().append(" "); //ERROR.Only const methods of
                       //the object can be invoked.
    get().size(); //OK

    std::string str = get();
    str.append(" "); // OK.
}

Const qualifying return types needlessly could even be detrimental to performance as it could prevent compilers from performing optimizations.


6) It is also possible to overload functions based on constness of the function itself or of its parameters.

Compilers generally allow overloads based on constness of parameters that are pointers and references.

   void func(int * a){}
   void func(const int* a){}
   void func(int & a){}
   void func(const int& a){}

But the below code would generate a compiler error. Both functions are considered to be the same and therefore the compiler would throw a function redefinition error.

   void func(int a){}

   void func(const int a){}

It is not allowed to overload functions based on the return type alone because the compilers generally do not differentiate between functions that only differ in their return types.

   int* func(){}
   const int* func(){}

But compilers do allow overloading class member functions as shown below. The second function itself is declared as a const.


class Something{
   int* func();
   const int* func() const;
}

7) ‘constexpr’ is another type specifier, added to the language in C++ 11, that people often confuse with ‘const’. When we declare an object as a ‘const’, we tell the compiler that its value will never change after initialization. But when an object is declared as ‘constexpr’, we basically hint to the compiler that its value can be determined at compile time so that the compiler can perform optimizations and that the object is fit to be used in constant expressions like in array size specifiers and in template parameters.

While ‘const’ can only be used with member methods, ‘constexpr’ can also be used with non-member methods. But, for a method to be declared as a constexpr, it must satisfy certain requirements as described here.


class Something{
    int member_a;
    static int static_a;
    public:

    int getter1() const {  // OK
        return member_a;
    }

    int getter2() constexpr {  // OK
        return member_a;
    }

    static int getter3() const{ // ERROR
        return static_a;
    }

    static int getter4() constexpr{ // OK
        return static_a;
    }
}

‘constexpr’ can also be used with class constructors that satisfy certain requirements. See this for more details.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s