Member Function cv-qualifier Overloading

suggest change

Functions within a class can be overloaded for when they are accessed through a cv-qualified reference to that class; this is most commonly used to overload for const, but can be used to overload for volatile and const volatile, too. This is because all non-static member functions take this as a hidden parameter, which the cv-qualifiers are applied to. This is most commonly used to overload for const, but can also be used for volatile and const volatile.

This is necessary because a member function can only be called if it is at least as cv-qualified as the instance it’s called on. While a non-const instance can call both const and non-const members, a const instance can only call const members. This allows a function to have different behaviour depending on the calling instance’s cv-qualifiers, and allows the programmer to disallow functions for an undesired cv-qualifier(s) by not providing a version with that qualifier(s).

A class with some basic print method could be const overloaded like so:

#include <iostream>

class Integer
{
    public:
        Integer(int i_): i{i_}{}

        void print()
        {
            std::cout << "int: " << i << std::endl;
        }

        void print() const
        {
            std::cout << "const int: " << i << std::endl;
        }

    protected:
        int i;
};

int main()
{
    Integer i{5};
    const Integer &ic = i;
    
    i.print(); // prints "int: 5"
    ic.print(); // prints "const int: 5"
}

This is a key tenet of const correctness: By marking member functions as const, they are allowed to be called on const instances, which in turn allows functions to take instances as const pointers/references if they don’t need to modify them. This allows code to specify whether it modifies state by taking unmodified parameters as const and modified parameters without cv-qualifiers, making code both safer and more readable.

class ConstCorrect 
{
  public:
    void good_func() const 
    {
        std::cout << "I care not whether the instance is const." << std::endl;
    }

    void bad_func() 
    {
        std::cout << "I can only be called on non-const, non-volatile instances." << std::endl;
    }
};

void i_change_no_state(const ConstCorrect& cc) 
{
    std::cout << "I can take either a const or a non-const ConstCorrect." << std::endl;
    cc.good_func(); // Good.  Can be called from const or non-const instance.
    cc.bad_func();  // Error.  Can only be called from non-const instance.
}

void const_incorrect_func(ConstCorrect& cc) 
{
    cc.good_func(); // Good.  Can be called from const or non-const instance.
    cc.bad_func();  // Good.  Can only be called from non-const instance.
}

A common usage of this is declaring accessors as const, and mutators as non-const.


No class members can be modified within a const member function. If there is some member that you really need to modify, such as locking a std::mutex, you can declare it as mutable:

class Integer
{
    public:
        Integer(int i_): i{i_}{}

        int get() const
        {
            std::lock_guard<std::mutex> lock{mut};
            return i;
        }

        void set(int i_)
        {
            std::lock_guard<std::mutex> lock{mut};
            i = i_;
        }

    protected:
        int i;
        mutable std::mutex mut;
};

Feedback about page:

Feedback:
Optional: your email if you want me to get back to you:


Function overloading:
* Member Function cv-qualifier Overloading

Table Of Contents
8 Arrays
11 Loops
39 Streams
51 Unions
54 Function overloading
56 Lambdas
60 SFINAE
62 RAII
67 Sorting
84 RTTI
87 Scopes
104 Profiling
107 Recursion
117 Iteration
125 Alignment
134 Semaphore
136 Debugging
139 Mutexes
142 decltype