Overloading on const and volatile- why does it work by reference? - c++

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

Related

Does pass-by-reference decay into pass-by-pointer in some cases?

I've looked for an answer to this one, but I can't seem to find anything, so I'm asking here:
Do reference parameters decay into pointers where it is logically necessary?
Let me explain what I mean:
If I declare a function with a reference to an int as a parameter:
void sum(int& a, const int& b) { a += b; }
(assuming that this won't be inlined)
The logical assumption would be that calling this function can be optimized by not passing any parameters, but by letting the function access the variables that are already on the stack. Changing these directly prevents the need for passing pointers.
Problem with this is that (again, assuming this doesn't get inlined), if the function is called from a ton of different places, the relevant values for each call are potentially in different places in the stack, which means the call can't be optimized.
Does that mean that, in those cases (which could potentially make up the majority of one's cases if the function is called from a ton of different places in the code), the reference decays into a pointer, which gets passed to the function and used to influence the variables in the outer scope?
Bonus question: If this is true, does that mean I should consider caching referenced parameters inside of function bodies, so that I avoid the hidden dereferences that come with passing these references? I would then conservatively access the actual reference parameters, only when I need to actually write something to them. Is this approach warranted or is it best to trust the compiler to cache the values for me if it deems the cost of dereferencing higher than the cost of copying them one time?
Code for bonus question:
void sum(int& a, const int& b) {
int aCached = a;
// Do processing that required reading from a with aCached.
// Do processing the requires writing to a with the a reference.
a += b;
}
Bonus bonus question: Is it safe to assume (assuming everything above is true), that, when "const int& b" is passed, the compiler will be smart enough to pass b by value when passing by pointer isn't efficient enough? My reasoning behind this is that values are ok for "const int& b" because you never try to write to it, only read.
The compiler can decide to implement references as pointers, or inlining or any other method it chooses to use. In terms of performance, it's irrelevant. The compiler can and will do whatever it wants to when it comes to optimization. The compiler can implement your reference as a pass-by-value if it wants to (and if it's valid to do so in the specific situation).
Caching the result won't help because the compiler will do that anyways.
If you want to explicitly tell the compiler that the value might change (because of another thread that has access to the same pointer), you need to use the keyword volatile (or std::atomic if you're not already using a std::mutex).
Edit: The keyword "volatile" is never required for multithreading. std::mutex is enough.
If you don't use the keyword volatile, the compiler will almost certainly cache the result for you (if appropriate).
There are, however, at least 2 actual differences in the rules between pointers and references.
Taking the address (pointer) of a temporary value (rvalue) is undefined behavior in C++.
References are immutable, sometimes need to be wrapped in std::ref.
Here I'll provide examples for both differences.
This code using references is valid:
static int do_stuff(const int& i)
{
}
int main()
{
do_stuff(5);
return 0;
}
But this code has undefined behavior (in practice it will probably still work):
static int do_stuff(const int* i)
{
}
int main()
{
do_stuff(&5);
return 0;
}
That's because taking the address of a temporary value (non lvalue) is undefined behavior in C++. The value is not guaranteed to have an address. Note that taking the address like this is valid:
static int do_stuff(const int& i)
{
const int *ptr = &i;
}
int main()
{
do_stuff(5);
return 0;
}
Because inside of the function do_stuff, the variable has a name and is therefore an lvalue. That means that by the time it's inside of do_stuff it's guaranteed to have an address.
So that's one difference between a pointer an a reference in C++.
There is another difference, and that is the constness / immutability.
On important thing to know about in C++ is the use for the helper function std::ref.
Consider the following code:
#include <functional>
#include <thread>
#include <future>
#include <chrono>
#include <iostream>
struct important_t
{
int val = 0;
};
static void work(const volatile important_t& arg)
{
std::cout << "Doing work..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
}
int main()
{
important_t my_object;
{
std::cout << "Starting thread" << std::endl;
std::future<void> t = std::async(std::launch::async, work, std::ref(my_object));
std::cout << "Waiting for thread to finish" << std::endl;
}
return 0;
}
The above code will compile just fine, and is perfectly valid C++ code.
But if you wrote it like this:
std::future<void> t = std::async(std::launch::async, work, my_object);
It wouldn't compile. That's because of the std::ref.
The reason that the code doesn't compile without std::ref is that the function std::async (and also std::thread) requires each and every one of the objects being passed as function parameters to be copy constructible.
That demonstrates a fundamental difference between references and all other built-in types in C++. References are immutable, and there's no way to make them editable.
Consider the following code:
#include <iostream>
int main()
{
// Perfectly valid
// Prints 5
{
int val = 0;
int& val_ref = val;
val_ref = 5;
std::cout << val << std::endl;
}
// Compiler error:
// A reference must always be initialized.
// A reference will always point to the same value throughout its lifetime.
{
int val = 0;
int& val_ref;
val_ref = val;
val_ref = 5;
std::cout << val << std::endl;
}
// We will encounter a similar compiler error with a const pointer:
// A const value must always be initialized.
// A const pointer will always point to the same value throughout its lifetime.
{
int val = 0;
int *const val_ptr;
val_ref = &val;
val_ref = 5;
std::cout << val << std::endl;
}
return 0;
}
That leads to the conclusion that a reference is not the same thing as a pointer in C++. It's almost the same thing as a const pointer.
Just a little bit of clarification:
A const pointer to a const int:
void do_stuff(const int *const val)
{
int i;
val = 5; // Error
val = &i; // Error
}
A const pointer to an int:
void do_stuff(int *const val)
{
int i;
val = 5; // Allowed. The int is not const.
val = &i; // Error
}
A pointer to a const int:
void do_stuff(const int* val)
{
int i;
val = 5; // Error
val = &i; // Allowed
}
An int reference in C++ is the closest thing to a const pointer to an int. The int is editable, the pointer is not.

On the weak semantics of references-to-const (and pointers-to-const)

This has probably been already asked.
Why is it allowed to assign a reference-to-const to a non-const variable?
Why is this allowed
int mut {0};
const int & r_to_c {mut};
mut = 1;
// now r_to_c changed to 1!
// But it was supposed to be a reference to something constant!
?
Sure, I cannot mutate the value from the reference-to-const itself. I cannot
r_to_c = 2;
but isn't the const qualification enforcing too little? I would expect, from a promise of const-ness, that binding to mutable variables was disallowed.
Otherwise what guarantees is const giving me? They seem pretty weak, and it seems that this could easily trick programmers to shoot themselves in their foot.
I know that C++ has a reputation for allowing people to shoot themselves in their foot. I don't have a problem with allowing dangerous things. In this case, my problem is that in this case it seems that it is purposefully deceiving, given that the semantics of const here is not the one would expect it.
Mine is a question about the compiler and the language semantics, not about references in particular (I could have asked the same question using a pointer-to-const that is assigned to the address of a non-const variable. Like int mut{0}; const int * p_to_c{&mut};).
Why is the semantics of a reference-to-const (or pointer-to-const) just "you can't use this particular window to modify the thing you see (but if you have other windows that are non-const, you can modify it)" instead of a more powerful "this can only be a window to something that was declared constant and that the compiler guarantees it stays constant"?
[Note on terminology: I use the expression "reference-to-const" instead of "const reference" because a "const reference", interpreted as T& const - consistently with calling T* const a "const pointer" -, does not exist.]
but isn't the const qualification enforcing too little? I would expect, from a promise of const-ness, that binding to mutable variables was disallowed.
No it is not "too little". You are expecting the wrong thing.
First, whether you bind a const reference does not make the object itself const. That would be strange:
void foo(int& x) {
static const int& y = x;
}
When I call foo:
int x = 42;
foo(x);
I cannot know whether somebody else will keep a const reference to my non-const x.
Otherwise what guarantees is const giving me?
You cannot modify something via a const reference:
void bar(const int& x);
int x = 0;
bar(x);
When I call a function that takes a const& then I know that it will not modify my (non-const) parameter. If const references would not bind to non-const objects then there would be no way to make this last example work, i.e. you could pass non-const objects only to functions that do modify them, but not to functions that do not modify them.
P.S. I can understand your confusion. It is sometimes overlooked that holding a constant reference does not imply that the object cannot be modified. Consider this example:
#include <cstddef>
#include <iostream>
struct foo {
const int& x;
};
int main() {
int y = 0;
foo f{x};
std::cout << f.x; // prints 0
y = 42;
std::cout << f.x; // prints 42
}
Printing the value of the member to the screen yields two different results, even though foo::x is a constant reference! It is a "constant reference" not a "reference to a constant". What const actually means here: You cannot modify y through f.x.
The ability to bind a const-reference to a mutable variable is actually a very valuable feature to have in the language. Consider that we might want to have a mutable variable.
int mut {0};
// ... some time later
mut = 1;
This is perfectly reasonable; it's a variable that is going to change during the execution of the program.
Now let's say we want to print the value of this variable, and would like to write a function to do that.
void print(int param) // or 'int &' to avoid a copy,
// but the point here is that it's non-const
{
std::cout << param;
}
This is fine, but clearly the function is not changing the parameter. We would like that to be enforced so that mistakes like param = 42; are caught by the compiler. To do that, we would make param a const & parameter.
void print(int const & param);
It would be quite unfortunate if we couldn't call this function with arguments that are non-const. After all, we don't care that the parameter might be modified outside the function. We just want to say that the parameter is guaranteed not to be modified by print, and binding a const & to a mutable variable serves exactly that purpose.
A reference to a const object is not the same as a const reference to a non const object, but C++'s type system does not distinguish them.
This is sort of a violation of the LSP; it has the same kind of problem as a reference to a mutable square and rectangle do.
You can create "true const" but you need help at declaration.
template<class T>
struct true_const {
const T value;
};
a true_const<int>& or true_const<T> const& can be passed around as a reference, and nobody can edit it (without invoking UB) "behind your back".
Of course, a function taking a true const cannot also take a normal object.
void bob( true_const<int>& x ) {
auto local = x.value;
call_some_other_function();
assert(local == x.value); // guaranteed to be true
}
void bob( const int& x ) {
auto local = x;
call_some_other_function();
assert(local == x); // NOT guaranteed to be true
}
const fields in classes are truly const; modifying them is undefined behavior.
A thin wrapper around a type that is const within the class is thus a guarantee the data is const. Then take a reference to that.
Now, the true_const could use some operator support.
template<class T>
struct true_const {
const T value;
constexpr T const& get() const { return value; }
constexpr T const& operator*() const { return get(); }
constexpr T const* operator->() const { return std::addressof(value); }
constexpr operator T const&() const { return get(); }
// concepts-defended operator+,==, etc
};

Can I initialize a constant integer with a L-value?

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.

Why does const allow implicit conversion of references in arguments?

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.

Difference of function argument as (const int &) and (int & a) in C++

I know that if you write void function_name(int& a), then function will not do local copy of your variable passed as argument. Also have met in literature that you should write void function_name(const int & a) in order to say compiler, that I dont want the variable passed as argument to be copied.
So my question: what is the difference with this two cases (except that "const" ensures that the variable passed will not be changed by function!!!)???
You should use const in the signature whenever you do not need to write. Adding const to the signature has two effects: it tells the compiler that you want it to check and guarantee that you do not change that argument inside your function. The second effect is that enables external code to use your function passing objects that are themselves constant (and temporaries), enabling more uses of the same function.
At the same time, the const keyword is an important part of the documentation of your function/method: the function signature is explicitly saying what you intend to do with the argument, and whether it is safe to pass an object that is part of another object's invariants into your function: you are being explicit in that you will not mess with their object.
Using const forces a more strict set of requirements in your code (the function): you cannot modify the object, but at the same time is less restrictive in your callers, making your code more reusable.
void printr( int & i ) { std::cout << i << std::endl; }
void printcr( const int & i ) { std::cout << i << std::endl; }
int main() {
int x = 10;
const int y = 15;
printr( x );
//printr( y ); // passing y as non-const reference discards qualifiers
//printr( 5 ); // cannot bind a non-const reference to a temporary
printcr( x ); printcr( y ); printcr( 5 ); // all valid
}
So my question: what is the difference
with this two cases (except that
"const" enshures that the variable
passes will not be changed by
function!!!)???
That is the difference.
You state the difference right. You may also formulate it as:
If you want to specify that the function may change the argument (i.e. for init_to_big_number( int& i ) by specifying the argument by (variable) reference. When in doubt, specify it const.
Note that the benefit of not copying the argument is in performance, i.e. for 'expensive' objects. For built-in types like int it makes no sense to write void f( const int& i ). Passing the reference to the variable is just as expensive as passing the value.
There is a big difference in terms of parameter they could operate on,
Say you have a copy constructor for your class from int,
customeclass(const int & count){
//this constructor is able to create a class from 5,
//I mean from RValue as well as from LValue
}
customeclass( int & count){
//this constructor is not able to create a class from 5,
//I mean only from LValue
}
The const version can essentially operate on temporary values and non constant version could not operate on temporary, you would easily face issue when you miss out const where it is needed and use STL, but you get weired error telling it could not find the version that takes temporary. I recommend use const where ever you can.
They are used for different purposes. Passing a variable using const int& ensures you get the pass-by-copy semantics with much better performance. You are guaranteed that the called function (unless it does some crazy things using const_cast) will not modify your passed argument without creating a copy. int& is used when there are generally multiple return values from a function. In that case these can be used hold the results of the function.
I would say that
void cfunction_name(const X& a);
allows me to pass a reference to temporary object as follows
X make_X();
function_name(make_X());
While
void function_name(X& a);
fails to achieve this. with the following error
error: invalid initialization of non-const reference of type 'X&' from a temporary of type 'X'
leaving out the performance discussion, let the code speak!
void foo(){
const int i1 = 0;
int i2 = 0;
i1 = 123; //i gets red -> expression must be a modifiyble value
i2 = 123;
}
//the following two functions are OK
void foo( int i ) {
i = 123;
}
void foo( int & i ) {
i = 123;
}
//in the following two functions i gets red
//already your IDE (VS) knows that i should not be changed
//and it forces you not to assign a value to i
//more over you can change the constness of one variable, in different functions
//in the function where i is defined it could be a variable
//in another function it could be constant
void foo( const int i ) {
i = 123;
}
void foo( const int & i ) {
i = 123;
}
using "const" where it is needed has the following benefits:
* you can change the constness of one variable i, in different functions
in the function where i is defined it could be a variable
in another function it could be constant value.
* already your IDE knows that i should not be changed.
and it forces you not to assign a value to i
regards
Oops