I would say any local variable that doesn't change should be constexpr if possible and const reference if not. If you're going to modify a variable you really need to know if it's a reference to or a local copy. If you get it wrong your program's not going to do what you expect it to do. However if your variable is const it doesn't matter whether it's a reference or a copy.
The language supports this by allowing a const ref to bind to a temporary.
// some functions
int& by_ref();
int by_val();
int main(){
{
auto a = by_ref(); // takes a copy
const auto b = by_ref(); // also takes a copy
auto& c = by_ref(); // no copy
const auto& d = by_ref(); // no copy
}
{
auto a = by_val(); // might copy
const auto b = by_val(); // might copy
auto& c = by_val(); // error reference to temporary
const auto& d = by_val(); // allowed! no copy.
}
}
Notice that a, b and c all behave differently depending on whether the assignment is by reference or by value. But d is the same on both cases.
If a function returns a reference then d will just refer to that value, if it returns a value, then d will refer to a temporary object the lifetime of that object will be extended until d goes out of scope.
So by using const auto& wherever possible your code is more robust to changes in the rest of the code. You can change the return value from reference to value without breaking the code and from value to reference without incurring an extra copy.
If you construct an instance of a class in your function then using a const reference is a bit strange and might confuse someone reading it.
For a built in type is has already been pointed out that there's probably no performance benefit to marking it const but for a more complex type there are good reasons to do so. Consider the following.
class my_class{
protected:
struct my_data {
int x, y, z;
};
~my_class() = default;
private:
virtual my_data& data() = 0;
virtual const my_data& data() const = 0;
public:
auto& x() {
return data().x;
}
auto& y() {
return data().y;
}
auto& z() {
return data().z;
}
auto& x() const{
return data().x;
}
auto& y() const{
return data().y;
}
auto& z() const{
return data().z;
}
};
You'll notice that I've defined everything in my_class twice. I'm a lazy guy so I'm not going to do that without a good reason. The reason is that a const instance of my_class can only access the methods labelled const.
So I need to provide both const and non const versions of some functions and my natural instinct is to wonder whether I really need to do all that typing.
It's pretty obvious for this version that we need the const versions of all the functions. It's clearly an abstraction and I can't know how it's going to be implemented or used. It's reasonable to assume that someone will want a const my_class somewhere and they should still be allowed to use all the methods.
However as I normally prefer to abstract through templates I've sometimes got a little class that I'm only going to use locally or as a workaround for some weird bit of syntax so it can be really tempting to provide only the non const version of a function and only use non const instances.
Which works great until it stops working. When at some point somewhere a const version of something instantiates a const version of my class we get one of those war and peace style error messages that template meta programmers love so much.
So the moral is always define const and non const versions of member functions. The reasons are exactly the same as for the my_class example but there not always entirely obvious in every context. Unfortunately if it's not obvious why you should do something then it's usually not obvious that not doing it is what caused an error or a bug.
So if you always declare variables as const unless you need to alter them
you'll catch any class that doesn't have const versions of its functions. This means you'll be less reluctant to put them in in the first place because you know you're going to catch the error early enough that you have to fix it yourself.
Even if you didn't define the class yourself there might be subtly different versions of some methods for const and non const.
It shouldn't usually be the case that const and non const member functions of a class are dramatically different but there's nothing in the language that says they have to be the same. So by declaring a local variable const you could have slightly different behaviour and it's probably the const behaviour you want. The only example I can think of where it might incur a performance hit is in a multithreaded situation. Accessing a non const member function might involve taking a write-lock which would have to wait for all readers to exit, but the const version might only involve waiting for one writer to finish.
So there are good reasons to prefer const in many situations although for built in types and some small classes the arguments aren't quite as strong. I would recommend in this case to apply the unthinking rule always prefer const than to make a few exceptions with no great benefit.