This may sound like a silly question, but I was confused about this following behaviour:
void funcTakingRef(unsigned int& arg) { std::cout << arg; }
void funcTakingByValue(unsigned int arg) { std::cout << arg; }
int main()
{
int a = 7;
funcTakingByValue(a); // Works
funcTakingRef(a); // A reference of type "unsigned int &" (not const-qualified)
// cannot be initialized with a value of type "int"
}
After thinking about it this kind of makes sense because in passing by value a new variable is created and conversion can be done, but not so much when passing the actual address of a variable, as in C++ once variables are made their type can't really change. I thought it's similar to this case:
int a;
unsigned int* ptr = &a; // A value of type int* cannot be used to
// initialise an entity of type "unsigned int*"
But if I make ref function take a const the conversion works:
void funcTakingRef(const unsigned int& arg) { std::cout << arg; } // I can pass an int to this.
However not the same in the case of pointer:
const unsigned int* ptr = &a; // Doesn't work
I'm wondering what the reason for this is. I thought my reasoning was right that implicit conversion when passing by value made sense as a new variable is made, whereas because in C++ types never change once created you can't get an implicit conversion on a reference. But this doesn't seem to apply in a const reference parameter.
The point is the temporary.
References can't bind to variables with different type directly. For both cases int needs to be converted to unsigned int, which is a temporary (copied from int). The temporary unsigned int could be bound to lvalue-reference to const (i.e. const unsigned int&), (and its lifetime is extended to the lifetime of the reference,) but can't be bound to lvalue-reference to non-const (i.e. unsigned int&). e.g.
int a = 7;
const unsigned int& r1 = a; // fine; r1 binds to the temporary unsigned int created
// unsigned int& r2 = a; // not allowed, r2 can't bind to the temporary
// r2 = 10; // trying to modify the temporary which has nothing to do with a; doesn't make sense
const & allows the compiler to generate a temporary variable, which gets thrown away after the call (and the function can’t change it, as it is const).
For non-const, the function would be able to modify it, and the compiler would have to transfer it back to the type it came from, which would lead to all kinds of issues, so it’s not allowed/possible.
Related
I've got a question in my assignment that asks me to evaluate whether the following function call is correct. I'm not sure if a const int can be initialized with a variable of type const int&. I know that a const int can be initialized with another int, for example
int i=3; const int j=i
works perfectly fine, but I'm not sure if the following code is semantically correct (the line const int j=bar(++i);)
int foo (int& i) {return i+=2;}
const int& bar(int &i){ return i+=2;}
int main(){
int i=5;
const int j=bar(++i);
}
Yes it can. You can initialize an object with any value category.
When constructing the type, it will simply call its constructor with the correct overload, such as type(type&& other) or type(type const&).
For trivial types, it's always a copy. So as long as the types are compatible, no matter their value category, it will work.
Initializing a reference is different. You must have an expression with a compatible value category. For example, creating a mutable reference from your function won't work:
int& j = bar(++i); // won't compile, int& cannot be bound to int const&
This is because bar returns a reference to const, thus cannot be bound to a reference to mutable.
As a side note, even though it's an int constant, it is not a compile time constant anymore. Thus you won't be able to use it as an array size or template parameter.
To fix that you'd have to use constexpr, which will guarantee that the value of your variable is available at compile time.
int a = 9;
constexpr int b = a; // Won't work, `a` is a runtime value, `b` is compile time
constexpr int a = 1;
constexpr int b = a + 1; // Works! Both compile time values
A reference is basically just an alias to the object it is bound to (with possible indication of immutability by const). Since bar(++i) returns a reference bound to i, it is the same as if you initialized j by i after bar(++i) call:
bar(++i);
const int j = i;
Const reference just says that you cannot modify the bound object through that reference. But there is nothing in your code that would try doing this.
void DoWork(int n);
void DoWork(const int &n);
What's the difference?
The important difference is that when passing by const reference, no new object is created. In the function body, the parameter is effectively an alias for the object passed in.
Because the reference is a const reference the function body cannot directly change the value of that object. This has a similar property to passing by value where the function body also cannot change the value of the object that was passed in, in this case because the parameter is a copy.
There are crucial differences. If the parameter is a const reference, but the object passed it was not in fact const then the value of the object may be changed during the function call itself.
E.g.
int a;
void DoWork(const int &n)
{
a = n * 2; // If n was a reference to a, n will have been doubled
f(); // Might change the value of whatever n refers to
}
int main()
{
DoWork(a);
}
Also if the object passed in was not actually const then the function could (even if it is ill advised) change its value with a cast.
e.g.
void DoWork(const int &n)
{
const_cast<int&>(n) = 22;
}
This would cause undefined behaviour if the object passed in was actually const.
When the parameter is passed by const reference, extra costs include dereferencing, worse object locality, fewer opportunities for compile optimizing.
When the parameter is passed by value an extra cost is the need to create a parameter copy. Typically this is only of concern when the object type is large.
The difference is more prominent when you are passing a big struct/class:
struct MyData {
int a,b,c,d,e,f,g,h;
long array[1234];
};
void DoWork(MyData md);
void DoWork(const MyData& md);
When you use use 'normal' parameter, you pass the parameter by value and hence creating a copy of the parameter you pass. If you are using const reference, you pass it by reference and the original data is not copied.
In both cases, the original data cannot be modified from inside the function.
EDIT:
In certain cases, the original data might be able to get modified as pointed out by Charles Bailey in his answer.
There are three methods you can pass values in the function
Pass by value
void f(int n){
n = n + 10;
}
int main(){
int x = 3;
f(x);
cout << x << endl;
}
Output: 3. Disadvantage: When parameter x pass through f function then compiler creates a copy in memory in of x. So wastage of memory.
Pass by reference
void f(int& n){
n = n + 10;
}
int main(){
int x = 3;
f(x);
cout << x << endl;
}
Output: 13. It eliminate pass by value disadvantage, but if programmer do not want to change the value then use constant reference
Constant reference
void f(const int& n){
n = n + 10; // Error: assignment of read-only reference ‘n’
}
int main(){
int x = 3;
f(x);
cout << x << endl;
}
Output: Throw error at n = n + 10 because when we pass const reference parameter argument then it is read-only parameter, you cannot change value of n.
With
void DoWork(int n);
n is a copy of the value of the actual parameter, and it is legal to change the value of n within the function. With
void DoWork(const int &n);
n is a reference to the actual parameter, and it is not legal to change its value.
Since none of you mentioned nothing about the const keyword...
The const keyword modifies the type of a type declaration or the type of a function parameter, preventing the value from varying. (Source: MS)
In other words: passing a parameter by reference exposes it to modification by the callee. Using the const keyword prevents the modification.
The first method passes n by value, i.e. a copy of n is sent to the function. The second one passes n by reference which basically means that a pointer to the n with which the function is called is sent to the function.
For integral types like int it doesn't make much sense to pass as a const reference since the size of the reference is usually the same as the size of the reference (the pointer). In the cases where making a copy is expensive it's usually best to pass by const reference.
Firstly, there is no concept of cv-qualified references. So the terminology 'const reference' is not correct and is usually used to describle 'reference to const'. It is better to start talking about what is meant.
$8.3.2/1- "Cv-qualified references are ill-formed except when the
cv-qualifiers are introduced through the use of a typedef (7.1.3) or
of a template type argument (14.3), in which case the cv-qualifiers
are ignored."
Here are the differences
$13.1 - "Only the const and volatile type-specifiers at the outermost
level of the parameter type specification are ignored in this fashion;
const and volatile type-specifiers buried within a parameter type
specification are significant and can be used to distinguish
overloaded function declarations.112). In particular, for any type T,
“pointer to T,” “pointer to const T,” and “pointer to volatile T” are
considered distinct parameter types, as are “reference to T,”
“reference to const T,” and “reference to volatile T.”
void f(int &n){
cout << 1;
n++;
}
void f(int const &n){
cout << 2;
//n++; // Error!, Non modifiable lvalue
}
int main(){
int x = 2;
f(x); // Calls overload 1, after the call x is 3
f(2); // Calls overload 2
f(2.2); // Calls overload 2, a temporary of double is created $8.5/3
}
Also, you can use the const int& x to initialize it with r-value and this will cause that you can't change x or bind it with another values.
const int& x = 5; // x is a constant reference to r-value 5
x = 7; // expression is not a modifable value
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.
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.
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).
Does casting a variable to another type return a temporary copy of that variable? if so then why can't you reference the temporary variable to a function.
void func(int &i) //error converting parameter 1 from int to int&
{
}
int main()
{
double d = 6.8;
func(int(d));
}
Yes casting returns an rvalue (temporary value), but a mutable reference needs an lvalue.
Try this instead:
int main() {
double d = 6.8;
{
int v = d;
func(v);
d = v; // if the change needs to be reflected back to d.
// note that, even if `func` doesn't change `v`,
// `d` will always be truncated to 6.
}
}
If func is not going to modify i, the input argument should be a const reference, which can accept an rvalue.
void func(const int& i);
(but for primitives func(int i) is going to be more efficient.)
The problem is that when you do int(d) inside the call to func, it creates a temporary object. You can't bind a reference to temporary. At least, not until C++0x comes and we get rvalue references (some compilers support them already, but the implementations may not be totally solid). You need to have an int variable defined to store the converted value, then pass that to the function.
void DoWork(int n);
void DoWork(const int &n);
What's the difference?
The important difference is that when passing by const reference, no new object is created. In the function body, the parameter is effectively an alias for the object passed in.
Because the reference is a const reference the function body cannot directly change the value of that object. This has a similar property to passing by value where the function body also cannot change the value of the object that was passed in, in this case because the parameter is a copy.
There are crucial differences. If the parameter is a const reference, but the object passed it was not in fact const then the value of the object may be changed during the function call itself.
E.g.
int a;
void DoWork(const int &n)
{
a = n * 2; // If n was a reference to a, n will have been doubled
f(); // Might change the value of whatever n refers to
}
int main()
{
DoWork(a);
}
Also if the object passed in was not actually const then the function could (even if it is ill advised) change its value with a cast.
e.g.
void DoWork(const int &n)
{
const_cast<int&>(n) = 22;
}
This would cause undefined behaviour if the object passed in was actually const.
When the parameter is passed by const reference, extra costs include dereferencing, worse object locality, fewer opportunities for compile optimizing.
When the parameter is passed by value an extra cost is the need to create a parameter copy. Typically this is only of concern when the object type is large.
The difference is more prominent when you are passing a big struct/class:
struct MyData {
int a,b,c,d,e,f,g,h;
long array[1234];
};
void DoWork(MyData md);
void DoWork(const MyData& md);
When you use use 'normal' parameter, you pass the parameter by value and hence creating a copy of the parameter you pass. If you are using const reference, you pass it by reference and the original data is not copied.
In both cases, the original data cannot be modified from inside the function.
EDIT:
In certain cases, the original data might be able to get modified as pointed out by Charles Bailey in his answer.
There are three methods you can pass values in the function
Pass by value
void f(int n){
n = n + 10;
}
int main(){
int x = 3;
f(x);
cout << x << endl;
}
Output: 3. Disadvantage: When parameter x pass through f function then compiler creates a copy in memory in of x. So wastage of memory.
Pass by reference
void f(int& n){
n = n + 10;
}
int main(){
int x = 3;
f(x);
cout << x << endl;
}
Output: 13. It eliminate pass by value disadvantage, but if programmer do not want to change the value then use constant reference
Constant reference
void f(const int& n){
n = n + 10; // Error: assignment of read-only reference ‘n’
}
int main(){
int x = 3;
f(x);
cout << x << endl;
}
Output: Throw error at n = n + 10 because when we pass const reference parameter argument then it is read-only parameter, you cannot change value of n.
With
void DoWork(int n);
n is a copy of the value of the actual parameter, and it is legal to change the value of n within the function. With
void DoWork(const int &n);
n is a reference to the actual parameter, and it is not legal to change its value.
Since none of you mentioned nothing about the const keyword...
The const keyword modifies the type of a type declaration or the type of a function parameter, preventing the value from varying. (Source: MS)
In other words: passing a parameter by reference exposes it to modification by the callee. Using the const keyword prevents the modification.
The first method passes n by value, i.e. a copy of n is sent to the function. The second one passes n by reference which basically means that a pointer to the n with which the function is called is sent to the function.
For integral types like int it doesn't make much sense to pass as a const reference since the size of the reference is usually the same as the size of the reference (the pointer). In the cases where making a copy is expensive it's usually best to pass by const reference.
Firstly, there is no concept of cv-qualified references. So the terminology 'const reference' is not correct and is usually used to describle 'reference to const'. It is better to start talking about what is meant.
$8.3.2/1- "Cv-qualified references are ill-formed except when the
cv-qualifiers are introduced through the use of a typedef (7.1.3) or
of a template type argument (14.3), in which case the cv-qualifiers
are ignored."
Here are the differences
$13.1 - "Only the const and volatile type-specifiers at the outermost
level of the parameter type specification are ignored in this fashion;
const and volatile type-specifiers buried within a parameter type
specification are significant and can be used to distinguish
overloaded function declarations.112). In particular, for any type T,
“pointer to T,” “pointer to const T,” and “pointer to volatile T” are
considered distinct parameter types, as are “reference to T,”
“reference to const T,” and “reference to volatile T.”
void f(int &n){
cout << 1;
n++;
}
void f(int const &n){
cout << 2;
//n++; // Error!, Non modifiable lvalue
}
int main(){
int x = 2;
f(x); // Calls overload 1, after the call x is 3
f(2); // Calls overload 2
f(2.2); // Calls overload 2, a temporary of double is created $8.5/3
}
Also, you can use the const int& x to initialize it with r-value and this will cause that you can't change x or bind it with another values.
const int& x = 5; // x is a constant reference to r-value 5
x = 7; // expression is not a modifable value