In every ABI I know of, references are passed via something equivalent to pointers. So when the compiler cannot inline the function or otherwise must follow the ABI, it will pass pointers there.
Pointers are often larger than values; but more importantly, pointers do not point at registers, and while the top of the stack is almost always going to be in cache, what it points at may not. In addition, many ABIs have primitives passed via register, which can be faster than via memory.
The next problem is within the function. Whenever the code flow could possible modify an int, data from a const int& parameter must be reloaded! While the reference is to const, the data it refers to can be changed via other paths.
The most common ways this can happen is when you leave the code the complier can see while understanding the function body or modify memory through a global variable, or follow a pointer to touch an int elsewhere.
In comparison, an int argument whose address is not taken cannot be legally modified through other means than directly. This permits the compiler to understand it isn't being mutated.
This isn't just a problem for the complier trying to optimize and getting confused. Take something like:
struct ui{
enum{ defFontSize=9;};
std:optional<int> fontSize;
void reloadFontSize(){
fontSize=getFontSizePref();
fontSizeChanged(*fontSize),
}
void fontSizeChanged(int const& sz){
if(sz==defFontSize)
fontSize=std:nullopt;
else
fontSize=sz;
drawText(sz);
}
void drawText(int sz){
std::cout << "At size " << sz <<"\n";
}
};
and the optional, to whom we are passing a reference, gets destroyed and used after destruction.
A bug like this can be far less obvious than this. If we defaulted to passing by value, it could not happen.
mainare notconsteither.mainwere specified long before we had the time to get all the experience and to write the C++ core guidelines. I know companies where a developer has to explain in a code review why a variable is mutable and IMHO that's a step in the right direction.constis the default.