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.
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).
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.