C++ Overloading on constness and volatility


Passing a pointer argument to a T* parameter, if possible, is better than passing it to a const T* parameter.

struct Base {};
struct Derived : Base {};
void f(Base* pb);
void f(const Base* pb);
void f(const Derived* pd);
void f(bool b);

Base b;
f(&b); // f(Base*) is better than f(const Base*)
Derived d;
f(&d); // f(const Derived*) is better than f(Base*) though;
       // constness is only a "tie-breaker" rule

Likewise, passing an argument to a T& parameter, if possible, is better than passing it to a const T& parameter, even if both have exact match rank.

void f(int& r);
void f(const int& r);
int x;
f(x); // both overloads match exactly, but f(int&) is still better
const int y = 42;
f(y); // only f(const int&) is viable

This rule applies to const-qualified member functions as well, where it is important for allowing mutable access to non-const objects and immutable access to const objects.

class IntVector {
    // ...
    int* data() { return m_data; }
    const int* data() const { return m_data; }
    // ...
    int* m_data;
IntVector v1;
int* data1 = v1.data();       // Vector::data() is better than Vector::data() const;
                              // data1 can be used to modify the vector's data
const IntVector v2;
const int* data2 = v2.data(); // only Vector::data() const is viable;
                              // data2 can't be used to modify the vector's data

In the same way, a volatile overload will be less preferred than a non-volatile overload.

class AtomicInt {
    // ...
    int load();
    int load() volatile;
    // ...
AtomicInt a1;
a1.load(); // non-volatile overload preferred; no side effect
volatile AtomicInt a2;
a2.load(); // only volatile overload is viable; side effect
static_cast<volatile AtomicInt&>(a1).load(); // force volatile semantics for a1