Overloading on const and volatile- why does it wor

2019-01-26 13:36发布

问题:

I have the code:

#include "stdafx.h"
#include <iostream>

using namespace std;


void func(const int& a)
{
    std::cout << "func(const)" << std::endl;
}

void func(volatile int& a)
{
    std::cout << "func(volatile)" << std::endl;
}

void func(const volatile int& a)
{
    std::cout << "func(const volatile)" << std::endl;
}

int main()
{
    const int a = 0;
    const volatile int b = 0;
    volatile int c = 0;
    func(a);
    func(b);
    func(c);
    system("pause");
    return 0;
}

The above code shows overloading based on whether the parameters are const/volatile. However, if I were to change the parameters from int& to int, the code no longer compiles and I cannot overload based upon const/volatile parameter types. I dont get why we can overload based on const and volatile if the int is passed by reference, but not if its passed by value?

EDIT I should emphasise I understand what a reference does- I do not understand why a reference alias is allowed to overload on const but a normal int is not.

回答1:

Perhaps it is useful to take a step back from the functions and just look at the use-cases themselves.

First, we will define an integer and a constant integer for use in our examples:

int       anInt     = 1;
const int aConstInt = 1;

Next, we take a look at what happens when using these variables to set the values of other integers and constant integers:

int       a = anInt;     // This works, we can set an int's value
                         //  using an int
int       b = aConstInt; // This works, we can set an int's value
                         //  using a const int
const int c = anInt;     // This works, we can set a const int's value
                         //  using an int
const int d = aConstInt; // This works, we can set a const int's value
                         //  using a const int

As you can see, there is no way to resolve which overload of a function to select based on behavior (a const int can be accepted by both an int and a const int, and likewise an int can be accepted by both an int and a const int).

Next, we shall take a look at what happens when pass the first set of variables to references:

int& a = anInt;     // This works because we are using a
                    //  non-constant reference to access a
                    //  non-constant variable.
int& b = aConstInt; // This will NOT work because we are
                    //  trying to access a constant
                    //  variable through a non-constant
                    //  reference (i.e. we could
                    //  potentially change a constant
                    //  variable through the non-const
                    //  reference).

const int& c = anInt;     // This works because we are using a
                          //  constant reference (i.e. "I cannot
                          //  try to change the referenced
                          //  variable using this reference") to
                          //  a non-constant variable.
const int& d = aConstInt; // This will work because we are trying
                          //  to access a constant variable 
                          //  through a constant reference.

As you can see, there is some useful behavior that can be had out of distinguishing between an int reference and a const int reference (i.e. disallowing creation of a non-constant reference when a constant reference type is expected).



回答2:

The issue is that the top level const and/or volatile are ignored in overload resolution. So

void foo(const int);

is exactly the same as

void foo(int);

and similarly for volatile. This is a language rule, and it makes sense since the arguments are passed by value. On the other hand, reference to const/volatile or pointer to const/volatile have a different meaning: you are not allowed to call non-const/volatile methods on what they refer to or point to. Here, the const volatile are not top level.

void foo(int& i);       // can modify what i refers to, and this has effects outside of foo.
void foo(const int& i); // cannot modify what i refers to

The two above declarations have very different semantics, so the language makes them distinct concerning overload resolution.