A const& cannot be changed without const_cast by reference, but it can be changed. At any moment when the code leaves the “analysis range” of your compiler (perhaps calling a function for another compilation module or through a function pointer that cannot determine the value at compile time), it should assume that the value mentioned may have changed.
This cost optimization. And this can make it difficult to reason about possible errors or quirks in your code: the link is a non-local state, and functions that work only in the local state and do not produce any side effects are really easy to reason about. Making code easy to justify is a great blessing: more time is spent on maintaining and fixing the code than writing it, and the effort spent on performance is interchangeable (you can spend it where it's important, and not spend time on micro optimization in all over the world).
On the other hand, a value requires that the value be copied to local automatic storage, which has costs.
But if your object is cheap to copy, and you do not want the aforementioned effect to occur, always take it by value, as this makes it easier to work with compilers to understand the function.
Naturally, only when the value is cheap to copy. If you are expensive, or even if the cost of copying is unknown, this cost should be enough to take on const& .
Short version above: taking by value makes it easier for you and the compiler to talk about the state of the parameter.
There is one more reason. If your object is cheap to move, and you are still going to store a local copy, then when you reach the cost, efficiency increases. If you take std::string to const& , then create a local copy, you can create one std::string to pass the parameter, and the other is created for the local copy.
If you took std::string by default, only one copy will be created (and possibly moved).
For a specific example:
std::string some_external_state; void foo( std::string const& str ) { some_external_state = str; } void bar( std::string str ) { some_external_state = std::move(str); }
then we can compare:
int main() { foo("Hello world!"); bar("Goodbye cruel world."); }
calling foo creates a std::string containing "Hello world!" . Then it is copied to some_external_state . 2 copies are made, 1 line is discarded.
A call to bar directly creates the std::string parameter. Its state is then moved to some_external_state . 1 copy created, 1 move, 1 (empty) line discarded.
There are also certain security enhancements caused by this technique, since any distribution takes place outside of bar , and foo can throw an exception from the resource.
This is only applicable when a beautiful transfer will annoy or fail, when moving is cheap, when copying can be expensive, and when you know that you are almost certainly going to make a local copy of the parameter.
Finally, there are small types (for example, int ) that are not optimized ABI for direct copies faster than not optimized ABI for const& parameters. This mainly matters when coding interfaces that cannot or will not be optimized, and are usually micro-optimized.